mod signature_help;

use crate::{
    CodeAction, CompletionSource, CoreCompletion, CoreCompletionResponse, DocumentColor,
    DocumentHighlight, DocumentSymbol, Hover, HoverBlock, HoverBlockKind, InlayHint,
    InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
    LocationLink, LspAction, LspPullDiagnostics, MarkupContent, PrepareRenameResponse,
    ProjectTransaction, PulledDiagnostics, ResolveState,
    lsp_store::{LocalLspStore, LspStore},
};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use client::proto::{self, PeerId};
use clock::Global;
use collections::{HashMap, HashSet};
use futures::future;
use gpui::{App, AsyncApp, Entity, Task};
use language::{
    Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CharScopeContext,
    OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
    language_settings::{InlayHintKind, LanguageSettings, language_settings},
    point_from_lsp, point_to_lsp,
    proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
    range_from_lsp, range_to_lsp,
};
use lsp::{
    AdapterServerCapabilities, CodeActionKind, CodeActionOptions, CodeDescription,
    CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind,
    DiagnosticServerCapabilities, DocumentHighlightKind, LanguageServer, LanguageServerId,
    LinkedEditingRangeServerCapabilities, OneOf, RenameOptions, ServerCapabilities,
};
use serde_json::Value;
use signature_help::{lsp_to_proto_signature, proto_to_lsp_signature};
use std::{
    cmp::Reverse, collections::hash_map, mem, ops::Range, path::Path, str::FromStr, sync::Arc,
};
use text::{BufferId, LineEnding};
use util::{ResultExt as _, debug_panic};

pub use signature_help::SignatureHelp;

pub fn lsp_formatting_options(settings: &LanguageSettings) -> lsp::FormattingOptions {
    lsp::FormattingOptions {
        tab_size: settings.tab_size.into(),
        insert_spaces: !settings.hard_tabs,
        trim_trailing_whitespace: Some(settings.remove_trailing_whitespace_on_save),
        trim_final_newlines: Some(settings.ensure_final_newline_on_save),
        insert_final_newline: Some(settings.ensure_final_newline_on_save),
        ..lsp::FormattingOptions::default()
    }
}

pub fn file_path_to_lsp_url(path: &Path) -> Result<lsp::Uri> {
    match lsp::Uri::from_file_path(path) {
        Ok(url) => Ok(url),
        Err(()) => anyhow::bail!("Invalid file path provided to LSP request: {path:?}"),
    }
}

pub(crate) fn make_text_document_identifier(path: &Path) -> Result<lsp::TextDocumentIdentifier> {
    Ok(lsp::TextDocumentIdentifier {
        uri: file_path_to_lsp_url(path)?,
    })
}

pub(crate) fn make_lsp_text_document_position(
    path: &Path,
    position: PointUtf16,
) -> Result<lsp::TextDocumentPositionParams> {
    Ok(lsp::TextDocumentPositionParams {
        text_document: make_text_document_identifier(path)?,
        position: point_to_lsp(position),
    })
}

#[async_trait(?Send)]
pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug {
    type Response: 'static + Default + Send + std::fmt::Debug;
    type LspRequest: 'static + Send + lsp::request::Request;
    type ProtoRequest: 'static + Send + proto::RequestMessage;

    fn display_name(&self) -> &str;

    fn status(&self) -> Option<String> {
        None
    }

    fn to_lsp_params_or_response(
        &self,
        path: &Path,
        buffer: &Buffer,
        language_server: &Arc<LanguageServer>,
        cx: &App,
    ) -> Result<
        LspParamsOrResponse<<Self::LspRequest as lsp::request::Request>::Params, Self::Response>,
    > {
        if self.check_capabilities(language_server.adapter_server_capabilities()) {
            Ok(LspParamsOrResponse::Params(self.to_lsp(
                path,
                buffer,
                language_server,
                cx,
            )?))
        } else {
            Ok(LspParamsOrResponse::Response(Default::default()))
        }
    }

    /// When false, `to_lsp_params_or_response` default implementation will return the default response.
    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool;

    fn to_lsp(
        &self,
        path: &Path,
        buffer: &Buffer,
        language_server: &Arc<LanguageServer>,
        cx: &App,
    ) -> Result<<Self::LspRequest as lsp::request::Request>::Params>;

    async fn response_from_lsp(
        self,
        message: <Self::LspRequest as lsp::request::Request>::Result,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Self::Response>;

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;

    async fn from_proto(
        message: Self::ProtoRequest,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Self>;

    fn response_to_proto(
        response: Self::Response,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        buffer_version: &clock::Global,
        cx: &mut App,
    ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;

    async fn response_from_proto(
        self,
        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Self::Response>;

    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId>;
}

pub enum LspParamsOrResponse<P, R> {
    Params(P),
    Response(R),
}

#[derive(Debug)]
pub(crate) struct PrepareRename {
    pub position: PointUtf16,
}

#[derive(Debug)]
pub(crate) struct PerformRename {
    pub position: PointUtf16,
    pub new_name: String,
    pub push_to_history: bool,
}

#[derive(Debug, Clone, Copy)]
pub struct GetDefinitions {
    pub position: PointUtf16,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct GetDeclarations {
    pub position: PointUtf16,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct GetTypeDefinitions {
    pub position: PointUtf16,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct GetImplementations {
    pub position: PointUtf16,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct GetReferences {
    pub position: PointUtf16,
}

#[derive(Debug)]
pub(crate) struct GetDocumentHighlights {
    pub position: PointUtf16,
}

#[derive(Debug, Copy, Clone)]
pub(crate) struct GetDocumentSymbols;

#[derive(Clone, Debug)]
pub(crate) struct GetSignatureHelp {
    pub position: PointUtf16,
}

#[derive(Clone, Debug)]
pub(crate) struct GetHover {
    pub position: PointUtf16,
}

#[derive(Debug)]
pub(crate) struct GetCompletions {
    pub position: PointUtf16,
    pub context: CompletionContext,
    pub server_id: Option<lsp::LanguageServerId>,
}

#[derive(Clone, Debug)]
pub(crate) struct GetCodeActions {
    pub range: Range<Anchor>,
    pub kinds: Option<Vec<lsp::CodeActionKind>>,
}

#[derive(Debug)]
pub(crate) struct OnTypeFormatting {
    pub position: PointUtf16,
    pub trigger: String,
    pub options: lsp::FormattingOptions,
    pub push_to_history: bool,
}

#[derive(Clone, Debug)]
pub(crate) struct InlayHints {
    pub range: Range<Anchor>,
}

#[derive(Debug, Copy, Clone)]
pub(crate) struct GetCodeLens;

#[derive(Debug, Copy, Clone)]
pub(crate) struct GetDocumentColor;

impl GetCodeLens {
    pub(crate) fn can_resolve_lens(capabilities: &ServerCapabilities) -> bool {
        capabilities
            .code_lens_provider
            .as_ref()
            .and_then(|code_lens_options| code_lens_options.resolve_provider)
            .unwrap_or(false)
    }
}

#[derive(Debug)]
pub(crate) struct LinkedEditingRange {
    pub position: Anchor,
}

#[derive(Clone, Debug)]
pub(crate) struct GetDocumentDiagnostics {
    /// We cannot blindly rely on server's capabilities.diagnostic_provider, as they're a singular field, whereas
    /// a server can register multiple diagnostic providers post-mortem.
    pub dynamic_caps: DiagnosticServerCapabilities,
    pub previous_result_id: Option<String>,
}

#[async_trait(?Send)]
impl LspCommand for PrepareRename {
    type Response = PrepareRenameResponse;
    type LspRequest = lsp::request::PrepareRenameRequest;
    type ProtoRequest = proto::PrepareRename;

    fn display_name(&self) -> &str {
        "Prepare rename"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .rename_provider
            .is_some_and(|capability| match capability {
                OneOf::Left(enabled) => enabled,
                OneOf::Right(options) => options.prepare_provider.unwrap_or(false),
            })
    }

    fn to_lsp_params_or_response(
        &self,
        path: &Path,
        buffer: &Buffer,
        language_server: &Arc<LanguageServer>,
        cx: &App,
    ) -> Result<LspParamsOrResponse<lsp::TextDocumentPositionParams, PrepareRenameResponse>> {
        let rename_provider = language_server
            .adapter_server_capabilities()
            .server_capabilities
            .rename_provider;
        match rename_provider {
            Some(lsp::OneOf::Right(RenameOptions {
                prepare_provider: Some(true),
                ..
            })) => Ok(LspParamsOrResponse::Params(self.to_lsp(
                path,
                buffer,
                language_server,
                cx,
            )?)),
            Some(lsp::OneOf::Right(_)) => Ok(LspParamsOrResponse::Response(
                PrepareRenameResponse::OnlyUnpreparedRenameSupported,
            )),
            Some(lsp::OneOf::Left(true)) => Ok(LspParamsOrResponse::Response(
                PrepareRenameResponse::OnlyUnpreparedRenameSupported,
            )),
            _ => anyhow::bail!("Rename not supported"),
        }
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::TextDocumentPositionParams> {
        make_lsp_text_document_position(path, self.position)
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::PrepareRenameResponse>,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        _: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<PrepareRenameResponse> {
        buffer.read_with(&cx, |buffer, _| match message {
            Some(lsp::PrepareRenameResponse::Range(range))
            | Some(lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. }) => {
                let Range { start, end } = range_from_lsp(range);
                if buffer.clip_point_utf16(start, Bias::Left) == start.0
                    && buffer.clip_point_utf16(end, Bias::Left) == end.0
                {
                    Ok(PrepareRenameResponse::Success(
                        buffer.anchor_after(start)..buffer.anchor_before(end),
                    ))
                } else {
                    Ok(PrepareRenameResponse::InvalidPosition)
                }
            }
            Some(lsp::PrepareRenameResponse::DefaultBehavior { .. }) => {
                let snapshot = buffer.snapshot();
                let (range, _) = snapshot.surrounding_word(self.position, None);
                let range = snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end);
                Ok(PrepareRenameResponse::Success(range))
            }
            None => Ok(PrepareRenameResponse::InvalidPosition),
        })?
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
        proto::PrepareRename {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::PrepareRename,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: PrepareRenameResponse,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::PrepareRenameResponse {
        match response {
            PrepareRenameResponse::Success(range) => proto::PrepareRenameResponse {
                can_rename: true,
                only_unprepared_rename_supported: false,
                start: Some(language::proto::serialize_anchor(&range.start)),
                end: Some(language::proto::serialize_anchor(&range.end)),
                version: serialize_version(buffer_version),
            },
            PrepareRenameResponse::OnlyUnpreparedRenameSupported => proto::PrepareRenameResponse {
                can_rename: false,
                only_unprepared_rename_supported: true,
                start: None,
                end: None,
                version: vec![],
            },
            PrepareRenameResponse::InvalidPosition => proto::PrepareRenameResponse {
                can_rename: false,
                only_unprepared_rename_supported: false,
                start: None,
                end: None,
                version: vec![],
            },
        }
    }

    async fn response_from_proto(
        self,
        message: proto::PrepareRenameResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<PrepareRenameResponse> {
        if message.can_rename {
            buffer
                .update(&mut cx, |buffer, _| {
                    buffer.wait_for_version(deserialize_version(&message.version))
                })?
                .await?;
            if let (Some(start), Some(end)) = (
                message.start.and_then(deserialize_anchor),
                message.end.and_then(deserialize_anchor),
            ) {
                Ok(PrepareRenameResponse::Success(start..end))
            } else {
                anyhow::bail!(
                    "Missing start or end position in remote project PrepareRenameResponse"
                );
            }
        } else if message.only_unprepared_rename_supported {
            Ok(PrepareRenameResponse::OnlyUnpreparedRenameSupported)
        } else {
            Ok(PrepareRenameResponse::InvalidPosition)
        }
    }

    fn buffer_id_from_proto(message: &proto::PrepareRename) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for PerformRename {
    type Response = ProjectTransaction;
    type LspRequest = lsp::request::Rename;
    type ProtoRequest = proto::PerformRename;

    fn display_name(&self) -> &str {
        "Rename"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .rename_provider
            .is_some_and(|capability| match capability {
                OneOf::Left(enabled) => enabled,
                OneOf::Right(_options) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::RenameParams> {
        Ok(lsp::RenameParams {
            text_document_position: make_lsp_text_document_position(path, self.position)?,
            new_name: self.new_name.clone(),
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::WorkspaceEdit>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        mut cx: AsyncApp,
    ) -> Result<ProjectTransaction> {
        if let Some(edit) = message {
            let (_, lsp_server) =
                language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
            LocalLspStore::deserialize_workspace_edit(
                lsp_store,
                edit,
                self.push_to_history,
                lsp_server,
                &mut cx,
            )
            .await
        } else {
            Ok(ProjectTransaction::default())
        }
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
        proto::PerformRename {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            new_name: self.new_name.clone(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::PerformRename,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
            new_name: message.new_name,
            push_to_history: false,
        })
    }

    fn response_to_proto(
        response: ProjectTransaction,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::PerformRenameResponse {
        let transaction = lsp_store.buffer_store().update(cx, |buffer_store, cx| {
            buffer_store.serialize_project_transaction_for_peer(response, peer_id, cx)
        });
        proto::PerformRenameResponse {
            transaction: Some(transaction),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::PerformRenameResponse,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<ProjectTransaction> {
        let message = message.transaction.context("missing transaction")?;
        lsp_store
            .update(&mut cx, |lsp_store, cx| {
                lsp_store.buffer_store().update(cx, |buffer_store, cx| {
                    buffer_store.deserialize_project_transaction(message, self.push_to_history, cx)
                })
            })?
            .await
    }

    fn buffer_id_from_proto(message: &proto::PerformRename) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetDefinitions {
    type Response = Vec<LocationLink>;
    type LspRequest = lsp::request::GotoDefinition;
    type ProtoRequest = proto::GetDefinition;

    fn display_name(&self) -> &str {
        "Get definition"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .definition_provider
            .is_some_and(|capability| match capability {
                OneOf::Left(supported) => supported,
                OneOf::Right(_options) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::GotoDefinitionParams> {
        Ok(lsp::GotoDefinitionParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::GotoDefinitionResponse>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
        proto::GetDefinition {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetDefinition,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<LocationLink>,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::GetDefinitionResponse {
        let links = location_links_to_proto(response, lsp_store, peer_id, cx);
        proto::GetDefinitionResponse { links }
    }

    async fn response_from_proto(
        self,
        message: proto::GetDefinitionResponse,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_proto(message.links, lsp_store, cx).await
    }

    fn buffer_id_from_proto(message: &proto::GetDefinition) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetDeclarations {
    type Response = Vec<LocationLink>;
    type LspRequest = lsp::request::GotoDeclaration;
    type ProtoRequest = proto::GetDeclaration;

    fn display_name(&self) -> &str {
        "Get declaration"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .declaration_provider
            .is_some_and(|capability| match capability {
                lsp::DeclarationCapability::Simple(supported) => supported,
                lsp::DeclarationCapability::RegistrationOptions(..) => true,
                lsp::DeclarationCapability::Options(..) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::GotoDeclarationParams> {
        Ok(lsp::GotoDeclarationParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::GotoDeclarationResponse>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDeclaration {
        proto::GetDeclaration {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetDeclaration,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<LocationLink>,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::GetDeclarationResponse {
        let links = location_links_to_proto(response, lsp_store, peer_id, cx);
        proto::GetDeclarationResponse { links }
    }

    async fn response_from_proto(
        self,
        message: proto::GetDeclarationResponse,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_proto(message.links, lsp_store, cx).await
    }

    fn buffer_id_from_proto(message: &proto::GetDeclaration) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetImplementations {
    type Response = Vec<LocationLink>;
    type LspRequest = lsp::request::GotoImplementation;
    type ProtoRequest = proto::GetImplementation;

    fn display_name(&self) -> &str {
        "Get implementation"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .implementation_provider
            .is_some_and(|capability| match capability {
                lsp::ImplementationProviderCapability::Simple(enabled) => enabled,
                lsp::ImplementationProviderCapability::Options(_options) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::GotoImplementationParams> {
        Ok(lsp::GotoImplementationParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::GotoImplementationResponse>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_lsp(message, lsp_store, buffer, server_id, cx).await
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetImplementation {
        proto::GetImplementation {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetImplementation,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<LocationLink>,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::GetImplementationResponse {
        let links = location_links_to_proto(response, lsp_store, peer_id, cx);
        proto::GetImplementationResponse { links }
    }

    async fn response_from_proto(
        self,
        message: proto::GetImplementationResponse,
        project: Entity<LspStore>,
        _: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_proto(message.links, project, cx).await
    }

    fn buffer_id_from_proto(message: &proto::GetImplementation) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetTypeDefinitions {
    type Response = Vec<LocationLink>;
    type LspRequest = lsp::request::GotoTypeDefinition;
    type ProtoRequest = proto::GetTypeDefinition;

    fn display_name(&self) -> &str {
        "Get type definition"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        !matches!(
            &capabilities.server_capabilities.type_definition_provider,
            None | Some(lsp::TypeDefinitionProviderCapability::Simple(false))
        )
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::GotoTypeDefinitionParams> {
        Ok(lsp::GotoTypeDefinitionParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::GotoTypeDefinitionResponse>,
        project: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_lsp(message, project, buffer, server_id, cx).await
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetTypeDefinition {
        proto::GetTypeDefinition {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetTypeDefinition,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<LocationLink>,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::GetTypeDefinitionResponse {
        let links = location_links_to_proto(response, lsp_store, peer_id, cx);
        proto::GetTypeDefinitionResponse { links }
    }

    async fn response_from_proto(
        self,
        message: proto::GetTypeDefinitionResponse,
        project: Entity<LspStore>,
        _: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Vec<LocationLink>> {
        location_links_from_proto(message.links, project, cx).await
    }

    fn buffer_id_from_proto(message: &proto::GetTypeDefinition) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

fn language_server_for_buffer(
    lsp_store: &Entity<LspStore>,
    buffer: &Entity<Buffer>,
    server_id: LanguageServerId,
    cx: &mut AsyncApp,
) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
    lsp_store
        .update(cx, |lsp_store, cx| {
            buffer.update(cx, |buffer, cx| {
                lsp_store
                    .language_server_for_local_buffer(buffer, server_id, cx)
                    .map(|(adapter, server)| (adapter.clone(), server.clone()))
            })
        })?
        .context("no language server found for buffer")
}

pub async fn location_links_from_proto(
    proto_links: Vec<proto::LocationLink>,
    lsp_store: Entity<LspStore>,
    mut cx: AsyncApp,
) -> Result<Vec<LocationLink>> {
    let mut links = Vec::new();

    for link in proto_links {
        links.push(location_link_from_proto(link, lsp_store.clone(), &mut cx).await?)
    }

    Ok(links)
}

pub fn location_link_from_proto(
    link: proto::LocationLink,
    lsp_store: Entity<LspStore>,
    cx: &mut AsyncApp,
) -> Task<Result<LocationLink>> {
    cx.spawn(async move |cx| {
        let origin = match link.origin {
            Some(origin) => {
                let buffer_id = BufferId::new(origin.buffer_id)?;
                let buffer = lsp_store
                    .update(cx, |lsp_store, cx| {
                        lsp_store.wait_for_remote_buffer(buffer_id, cx)
                    })?
                    .await?;
                let start = origin
                    .start
                    .and_then(deserialize_anchor)
                    .context("missing origin start")?;
                let end = origin
                    .end
                    .and_then(deserialize_anchor)
                    .context("missing origin end")?;
                buffer
                    .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                    .await?;
                Some(Location {
                    buffer,
                    range: start..end,
                })
            }
            None => None,
        };

        let target = link.target.context("missing target")?;
        let buffer_id = BufferId::new(target.buffer_id)?;
        let buffer = lsp_store
            .update(cx, |lsp_store, cx| {
                lsp_store.wait_for_remote_buffer(buffer_id, cx)
            })?
            .await?;
        let start = target
            .start
            .and_then(deserialize_anchor)
            .context("missing target start")?;
        let end = target
            .end
            .and_then(deserialize_anchor)
            .context("missing target end")?;
        buffer
            .update(cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
            .await?;
        let target = Location {
            buffer,
            range: start..end,
        };
        Ok(LocationLink { origin, target })
    })
}

pub async fn location_links_from_lsp(
    message: Option<lsp::GotoDefinitionResponse>,
    lsp_store: Entity<LspStore>,
    buffer: Entity<Buffer>,
    server_id: LanguageServerId,
    mut cx: AsyncApp,
) -> Result<Vec<LocationLink>> {
    let message = match message {
        Some(message) => message,
        None => return Ok(Vec::new()),
    };

    let mut unresolved_links = Vec::new();
    match message {
        lsp::GotoDefinitionResponse::Scalar(loc) => {
            unresolved_links.push((None, loc.uri, loc.range));
        }

        lsp::GotoDefinitionResponse::Array(locs) => {
            unresolved_links.extend(locs.into_iter().map(|l| (None, l.uri, l.range)));
        }

        lsp::GotoDefinitionResponse::Link(links) => {
            unresolved_links.extend(links.into_iter().map(|l| {
                (
                    l.origin_selection_range,
                    l.target_uri,
                    l.target_selection_range,
                )
            }));
        }
    }

    let (_, language_server) = language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
    let mut definitions = Vec::new();
    for (origin_range, target_uri, target_range) in unresolved_links {
        let target_buffer_handle = lsp_store
            .update(&mut cx, |this, cx| {
                this.open_local_buffer_via_lsp(target_uri, language_server.server_id(), cx)
            })?
            .await?;

        cx.update(|cx| {
            let origin_location = origin_range.map(|origin_range| {
                let origin_buffer = buffer.read(cx);
                let origin_start =
                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
                let origin_end =
                    origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
                Location {
                    buffer: buffer.clone(),
                    range: origin_buffer.anchor_after(origin_start)
                        ..origin_buffer.anchor_before(origin_end),
                }
            });

            let target_buffer = target_buffer_handle.read(cx);
            let target_start =
                target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
            let target_end =
                target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
            let target_location = Location {
                buffer: target_buffer_handle,
                range: target_buffer.anchor_after(target_start)
                    ..target_buffer.anchor_before(target_end),
            };

            definitions.push(LocationLink {
                origin: origin_location,
                target: target_location,
            })
        })?;
    }
    Ok(definitions)
}

pub async fn location_link_from_lsp(
    link: lsp::LocationLink,
    lsp_store: &Entity<LspStore>,
    buffer: &Entity<Buffer>,
    server_id: LanguageServerId,
    cx: &mut AsyncApp,
) -> Result<LocationLink> {
    let (_, language_server) = language_server_for_buffer(lsp_store, buffer, server_id, cx)?;

    let (origin_range, target_uri, target_range) = (
        link.origin_selection_range,
        link.target_uri,
        link.target_selection_range,
    );

    let target_buffer_handle = lsp_store
        .update(cx, |lsp_store, cx| {
            lsp_store.open_local_buffer_via_lsp(target_uri, language_server.server_id(), cx)
        })?
        .await?;

    cx.update(|cx| {
        let origin_location = origin_range.map(|origin_range| {
            let origin_buffer = buffer.read(cx);
            let origin_start =
                origin_buffer.clip_point_utf16(point_from_lsp(origin_range.start), Bias::Left);
            let origin_end =
                origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left);
            Location {
                buffer: buffer.clone(),
                range: origin_buffer.anchor_after(origin_start)
                    ..origin_buffer.anchor_before(origin_end),
            }
        });

        let target_buffer = target_buffer_handle.read(cx);
        let target_start =
            target_buffer.clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
        let target_end =
            target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
        let target_location = Location {
            buffer: target_buffer_handle,
            range: target_buffer.anchor_after(target_start)
                ..target_buffer.anchor_before(target_end),
        };

        LocationLink {
            origin: origin_location,
            target: target_location,
        }
    })
}

pub fn location_links_to_proto(
    links: Vec<LocationLink>,
    lsp_store: &mut LspStore,
    peer_id: PeerId,
    cx: &mut App,
) -> Vec<proto::LocationLink> {
    links
        .into_iter()
        .map(|definition| location_link_to_proto(definition, lsp_store, peer_id, cx))
        .collect()
}

pub fn location_link_to_proto(
    location: LocationLink,
    lsp_store: &mut LspStore,
    peer_id: PeerId,
    cx: &mut App,
) -> proto::LocationLink {
    let origin = location.origin.map(|origin| {
        lsp_store
            .buffer_store()
            .update(cx, |buffer_store, cx| {
                buffer_store.create_buffer_for_peer(&origin.buffer, peer_id, cx)
            })
            .detach_and_log_err(cx);

        let buffer_id = origin.buffer.read(cx).remote_id().into();
        proto::Location {
            start: Some(serialize_anchor(&origin.range.start)),
            end: Some(serialize_anchor(&origin.range.end)),
            buffer_id,
        }
    });

    lsp_store
        .buffer_store()
        .update(cx, |buffer_store, cx| {
            buffer_store.create_buffer_for_peer(&location.target.buffer, peer_id, cx)
        })
        .detach_and_log_err(cx);

    let buffer_id = location.target.buffer.read(cx).remote_id().into();
    let target = proto::Location {
        start: Some(serialize_anchor(&location.target.range.start)),
        end: Some(serialize_anchor(&location.target.range.end)),
        buffer_id,
    };

    proto::LocationLink {
        origin,
        target: Some(target),
    }
}

#[async_trait(?Send)]
impl LspCommand for GetReferences {
    type Response = Vec<Location>;
    type LspRequest = lsp::request::References;
    type ProtoRequest = proto::GetReferences;

    fn display_name(&self) -> &str {
        "Find all references"
    }

    fn status(&self) -> Option<String> {
        Some("Finding references...".to_owned())
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        match &capabilities.server_capabilities.references_provider {
            Some(OneOf::Left(has_support)) => *has_support,
            Some(OneOf::Right(_)) => true,
            None => false,
        }
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::ReferenceParams> {
        Ok(lsp::ReferenceParams {
            text_document_position: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
            context: lsp::ReferenceContext {
                include_declaration: true,
            },
        })
    }

    async fn response_from_lsp(
        self,
        locations: Option<Vec<lsp::Location>>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        mut cx: AsyncApp,
    ) -> Result<Vec<Location>> {
        let mut references = Vec::new();
        let (_, language_server) =
            language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;

        if let Some(locations) = locations {
            for lsp_location in locations {
                let target_buffer_handle = lsp_store
                    .update(&mut cx, |lsp_store, cx| {
                        lsp_store.open_local_buffer_via_lsp(
                            lsp_location.uri,
                            language_server.server_id(),
                            cx,
                        )
                    })?
                    .await?;

                target_buffer_handle
                    .clone()
                    .read_with(&cx, |target_buffer, _| {
                        let target_start = target_buffer
                            .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
                        let target_end = target_buffer
                            .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
                        references.push(Location {
                            buffer: target_buffer_handle,
                            range: target_buffer.anchor_after(target_start)
                                ..target_buffer.anchor_before(target_end),
                        });
                    })?;
            }
        }

        Ok(references)
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
        proto::GetReferences {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetReferences,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<Location>,
        lsp_store: &mut LspStore,
        peer_id: PeerId,
        _: &clock::Global,
        cx: &mut App,
    ) -> proto::GetReferencesResponse {
        let locations = response
            .into_iter()
            .map(|definition| {
                lsp_store
                    .buffer_store()
                    .update(cx, |buffer_store, cx| {
                        buffer_store.create_buffer_for_peer(&definition.buffer, peer_id, cx)
                    })
                    .detach_and_log_err(cx);
                let buffer_id = definition.buffer.read(cx).remote_id();
                proto::Location {
                    start: Some(serialize_anchor(&definition.range.start)),
                    end: Some(serialize_anchor(&definition.range.end)),
                    buffer_id: buffer_id.into(),
                }
            })
            .collect();
        proto::GetReferencesResponse { locations }
    }

    async fn response_from_proto(
        self,
        message: proto::GetReferencesResponse,
        project: Entity<LspStore>,
        _: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Vec<Location>> {
        let mut locations = Vec::new();
        for location in message.locations {
            let buffer_id = BufferId::new(location.buffer_id)?;
            let target_buffer = project
                .update(&mut cx, |this, cx| {
                    this.wait_for_remote_buffer(buffer_id, cx)
                })?
                .await?;
            let start = location
                .start
                .and_then(deserialize_anchor)
                .context("missing target start")?;
            let end = location
                .end
                .and_then(deserialize_anchor)
                .context("missing target end")?;
            target_buffer
                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                .await?;
            locations.push(Location {
                buffer: target_buffer,
                range: start..end,
            })
        }
        Ok(locations)
    }

    fn buffer_id_from_proto(message: &proto::GetReferences) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetDocumentHighlights {
    type Response = Vec<DocumentHighlight>;
    type LspRequest = lsp::request::DocumentHighlightRequest;
    type ProtoRequest = proto::GetDocumentHighlights;

    fn display_name(&self) -> &str {
        "Get document highlights"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .document_highlight_provider
            .is_some_and(|capability| match capability {
                OneOf::Left(supported) => supported,
                OneOf::Right(_options) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::DocumentHighlightParams> {
        Ok(lsp::DocumentHighlightParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        lsp_highlights: Option<Vec<lsp::DocumentHighlight>>,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        _: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<DocumentHighlight>> {
        buffer.read_with(&cx, |buffer, _| {
            let mut lsp_highlights = lsp_highlights.unwrap_or_default();
            lsp_highlights.sort_unstable_by_key(|h| (h.range.start, Reverse(h.range.end)));
            lsp_highlights
                .into_iter()
                .map(|lsp_highlight| {
                    let start = buffer
                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.start), Bias::Left);
                    let end = buffer
                        .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left);
                    DocumentHighlight {
                        range: buffer.anchor_after(start)..buffer.anchor_before(end),
                        kind: lsp_highlight
                            .kind
                            .unwrap_or(lsp::DocumentHighlightKind::READ),
                    }
                })
                .collect()
        })
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentHighlights {
        proto::GetDocumentHighlights {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetDocumentHighlights,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Vec<DocumentHighlight>,
        _: &mut LspStore,
        _: PeerId,
        _: &clock::Global,
        _: &mut App,
    ) -> proto::GetDocumentHighlightsResponse {
        let highlights = response
            .into_iter()
            .map(|highlight| proto::DocumentHighlight {
                start: Some(serialize_anchor(&highlight.range.start)),
                end: Some(serialize_anchor(&highlight.range.end)),
                kind: match highlight.kind {
                    DocumentHighlightKind::TEXT => proto::document_highlight::Kind::Text.into(),
                    DocumentHighlightKind::WRITE => proto::document_highlight::Kind::Write.into(),
                    DocumentHighlightKind::READ => proto::document_highlight::Kind::Read.into(),
                    _ => proto::document_highlight::Kind::Text.into(),
                },
            })
            .collect();
        proto::GetDocumentHighlightsResponse { highlights }
    }

    async fn response_from_proto(
        self,
        message: proto::GetDocumentHighlightsResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Vec<DocumentHighlight>> {
        let mut highlights = Vec::new();
        for highlight in message.highlights {
            let start = highlight
                .start
                .and_then(deserialize_anchor)
                .context("missing target start")?;
            let end = highlight
                .end
                .and_then(deserialize_anchor)
                .context("missing target end")?;
            buffer
                .update(&mut cx, |buffer, _| buffer.wait_for_anchors([start, end]))?
                .await?;
            let kind = match proto::document_highlight::Kind::from_i32(highlight.kind) {
                Some(proto::document_highlight::Kind::Text) => DocumentHighlightKind::TEXT,
                Some(proto::document_highlight::Kind::Read) => DocumentHighlightKind::READ,
                Some(proto::document_highlight::Kind::Write) => DocumentHighlightKind::WRITE,
                None => DocumentHighlightKind::TEXT,
            };
            highlights.push(DocumentHighlight {
                range: start..end,
                kind,
            });
        }
        Ok(highlights)
    }

    fn buffer_id_from_proto(message: &proto::GetDocumentHighlights) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetDocumentSymbols {
    type Response = Vec<DocumentSymbol>;
    type LspRequest = lsp::request::DocumentSymbolRequest;
    type ProtoRequest = proto::GetDocumentSymbols;

    fn display_name(&self) -> &str {
        "Get document symbols"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .document_symbol_provider
            .is_some_and(|capability| match capability {
                OneOf::Left(supported) => supported,
                OneOf::Right(_options) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::DocumentSymbolParams> {
        Ok(lsp::DocumentSymbolParams {
            text_document: make_text_document_identifier(path)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        lsp_symbols: Option<lsp::DocumentSymbolResponse>,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: LanguageServerId,
        _: AsyncApp,
    ) -> Result<Vec<DocumentSymbol>> {
        let Some(lsp_symbols) = lsp_symbols else {
            return Ok(Vec::new());
        };

        let symbols: Vec<_> = match lsp_symbols {
            lsp::DocumentSymbolResponse::Flat(symbol_information) => symbol_information
                .into_iter()
                .map(|lsp_symbol| DocumentSymbol {
                    name: lsp_symbol.name,
                    kind: lsp_symbol.kind,
                    range: range_from_lsp(lsp_symbol.location.range),
                    selection_range: range_from_lsp(lsp_symbol.location.range),
                    children: Vec::new(),
                })
                .collect(),
            lsp::DocumentSymbolResponse::Nested(nested_responses) => {
                fn convert_symbol(lsp_symbol: lsp::DocumentSymbol) -> DocumentSymbol {
                    DocumentSymbol {
                        name: lsp_symbol.name,
                        kind: lsp_symbol.kind,
                        range: range_from_lsp(lsp_symbol.range),
                        selection_range: range_from_lsp(lsp_symbol.selection_range),
                        children: lsp_symbol
                            .children
                            .map(|children| {
                                children.into_iter().map(convert_symbol).collect::<Vec<_>>()
                            })
                            .unwrap_or_default(),
                    }
                }
                nested_responses.into_iter().map(convert_symbol).collect()
            }
        };
        Ok(symbols)
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentSymbols {
        proto::GetDocumentSymbols {
            project_id,
            buffer_id: buffer.remote_id().into(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetDocumentSymbols,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self)
    }

    fn response_to_proto(
        response: Vec<DocumentSymbol>,
        _: &mut LspStore,
        _: PeerId,
        _: &clock::Global,
        _: &mut App,
    ) -> proto::GetDocumentSymbolsResponse {
        let symbols = response
            .into_iter()
            .map(|symbol| {
                fn convert_symbol_to_proto(symbol: DocumentSymbol) -> proto::DocumentSymbol {
                    proto::DocumentSymbol {
                        name: symbol.name.clone(),
                        kind: unsafe { mem::transmute::<lsp::SymbolKind, i32>(symbol.kind) },
                        start: Some(proto::PointUtf16 {
                            row: symbol.range.start.0.row,
                            column: symbol.range.start.0.column,
                        }),
                        end: Some(proto::PointUtf16 {
                            row: symbol.range.end.0.row,
                            column: symbol.range.end.0.column,
                        }),
                        selection_start: Some(proto::PointUtf16 {
                            row: symbol.selection_range.start.0.row,
                            column: symbol.selection_range.start.0.column,
                        }),
                        selection_end: Some(proto::PointUtf16 {
                            row: symbol.selection_range.end.0.row,
                            column: symbol.selection_range.end.0.column,
                        }),
                        children: symbol
                            .children
                            .into_iter()
                            .map(convert_symbol_to_proto)
                            .collect(),
                    }
                }
                convert_symbol_to_proto(symbol)
            })
            .collect::<Vec<_>>();

        proto::GetDocumentSymbolsResponse { symbols }
    }

    async fn response_from_proto(
        self,
        message: proto::GetDocumentSymbolsResponse,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Vec<DocumentSymbol>> {
        let mut symbols = Vec::with_capacity(message.symbols.len());
        for serialized_symbol in message.symbols {
            fn deserialize_symbol_with_children(
                serialized_symbol: proto::DocumentSymbol,
            ) -> Result<DocumentSymbol> {
                let kind =
                    unsafe { mem::transmute::<i32, lsp::SymbolKind>(serialized_symbol.kind) };

                let start = serialized_symbol.start.context("invalid start")?;
                let end = serialized_symbol.end.context("invalid end")?;

                let selection_start = serialized_symbol
                    .selection_start
                    .context("invalid selection start")?;
                let selection_end = serialized_symbol
                    .selection_end
                    .context("invalid selection end")?;

                Ok(DocumentSymbol {
                    name: serialized_symbol.name,
                    kind,
                    range: Unclipped(PointUtf16::new(start.row, start.column))
                        ..Unclipped(PointUtf16::new(end.row, end.column)),
                    selection_range: Unclipped(PointUtf16::new(
                        selection_start.row,
                        selection_start.column,
                    ))
                        ..Unclipped(PointUtf16::new(selection_end.row, selection_end.column)),
                    children: serialized_symbol
                        .children
                        .into_iter()
                        .filter_map(|symbol| deserialize_symbol_with_children(symbol).ok())
                        .collect::<Vec<_>>(),
                })
            }

            symbols.push(deserialize_symbol_with_children(serialized_symbol)?);
        }

        Ok(symbols)
    }

    fn buffer_id_from_proto(message: &proto::GetDocumentSymbols) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetSignatureHelp {
    type Response = Option<SignatureHelp>;
    type LspRequest = lsp::SignatureHelpRequest;
    type ProtoRequest = proto::GetSignatureHelp;

    fn display_name(&self) -> &str {
        "Get signature help"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .signature_help_provider
            .is_some()
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _cx: &App,
    ) -> Result<lsp::SignatureHelpParams> {
        Ok(lsp::SignatureHelpParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            context: None,
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::SignatureHelp>,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Self::Response> {
        let Some(message) = message else {
            return Ok(None);
        };
        cx.update(|cx| {
            SignatureHelp::new(
                message,
                Some(lsp_store.read(cx).languages.clone()),
                Some(id),
                cx,
            )
        })
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
        let offset = buffer.point_utf16_to_offset(self.position);
        proto::GetSignatureHelp {
            project_id,
            buffer_id: buffer.remote_id().to_proto(),
            position: Some(serialize_anchor(&buffer.anchor_after(offset))),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        payload: Self::ProtoRequest,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&payload.version))
            })?
            .await
            .with_context(|| format!("waiting for version for buffer {}", buffer.entity_id()))?;
        let buffer_snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
        Ok(Self {
            position: payload
                .position
                .and_then(deserialize_anchor)
                .context("invalid position")?
                .to_point_utf16(&buffer_snapshot),
        })
    }

    fn response_to_proto(
        response: Self::Response,
        _: &mut LspStore,
        _: PeerId,
        _: &Global,
        _: &mut App,
    ) -> proto::GetSignatureHelpResponse {
        proto::GetSignatureHelpResponse {
            signature_help: response
                .map(|signature_help| lsp_to_proto_signature(signature_help.original_data)),
        }
    }

    async fn response_from_proto(
        self,
        response: proto::GetSignatureHelpResponse,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        cx: AsyncApp,
    ) -> Result<Self::Response> {
        cx.update(|cx| {
            response
                .signature_help
                .map(proto_to_lsp_signature)
                .and_then(|signature| {
                    SignatureHelp::new(
                        signature,
                        Some(lsp_store.read(cx).languages.clone()),
                        None,
                        cx,
                    )
                })
        })
    }

    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetHover {
    type Response = Option<Hover>;
    type LspRequest = lsp::request::HoverRequest;
    type ProtoRequest = proto::GetHover;

    fn display_name(&self) -> &str {
        "Get hover"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        match capabilities.server_capabilities.hover_provider {
            Some(lsp::HoverProviderCapability::Simple(enabled)) => enabled,
            Some(lsp::HoverProviderCapability::Options(_)) => true,
            None => false,
        }
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::HoverParams> {
        Ok(lsp::HoverParams {
            text_document_position_params: make_lsp_text_document_position(path, self.position)?,
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::Hover>,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        _: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Self::Response> {
        let Some(hover) = message else {
            return Ok(None);
        };

        let (language, range) = buffer.read_with(&cx, |buffer, _| {
            (
                buffer.language().cloned(),
                hover.range.map(|range| {
                    let token_start =
                        buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
                    let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
                    buffer.anchor_after(token_start)..buffer.anchor_before(token_end)
                }),
            )
        })?;

        fn hover_blocks_from_marked_string(marked_string: lsp::MarkedString) -> Option<HoverBlock> {
            let block = match marked_string {
                lsp::MarkedString::String(content) => HoverBlock {
                    text: content,
                    kind: HoverBlockKind::Markdown,
                },
                lsp::MarkedString::LanguageString(lsp::LanguageString { language, value }) => {
                    HoverBlock {
                        text: value,
                        kind: HoverBlockKind::Code { language },
                    }
                }
            };
            if block.text.is_empty() {
                None
            } else {
                Some(block)
            }
        }

        let contents = match hover.contents {
            lsp::HoverContents::Scalar(marked_string) => {
                hover_blocks_from_marked_string(marked_string)
                    .into_iter()
                    .collect()
            }
            lsp::HoverContents::Array(marked_strings) => marked_strings
                .into_iter()
                .filter_map(hover_blocks_from_marked_string)
                .collect(),
            lsp::HoverContents::Markup(markup_content) => vec![HoverBlock {
                text: markup_content.value,
                kind: if markup_content.kind == lsp::MarkupKind::Markdown {
                    HoverBlockKind::Markdown
                } else {
                    HoverBlockKind::PlainText
                },
            }],
        };

        Ok(Some(Hover {
            contents,
            range,
            language,
        }))
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
        proto::GetHover {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            version: serialize_version(&buffer.version),
        }
    }

    async fn from_proto(
        message: Self::ProtoRequest,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
        })
    }

    fn response_to_proto(
        response: Self::Response,
        _: &mut LspStore,
        _: PeerId,
        _: &clock::Global,
        _: &mut App,
    ) -> proto::GetHoverResponse {
        if let Some(response) = response {
            let (start, end) = if let Some(range) = response.range {
                (
                    Some(language::proto::serialize_anchor(&range.start)),
                    Some(language::proto::serialize_anchor(&range.end)),
                )
            } else {
                (None, None)
            };

            let contents = response
                .contents
                .into_iter()
                .map(|block| proto::HoverBlock {
                    text: block.text,
                    is_markdown: block.kind == HoverBlockKind::Markdown,
                    language: if let HoverBlockKind::Code { language } = block.kind {
                        Some(language)
                    } else {
                        None
                    },
                })
                .collect();

            proto::GetHoverResponse {
                start,
                end,
                contents,
            }
        } else {
            proto::GetHoverResponse {
                start: None,
                end: None,
                contents: Vec::new(),
            }
        }
    }

    async fn response_from_proto(
        self,
        message: proto::GetHoverResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self::Response> {
        let contents: Vec<_> = message
            .contents
            .into_iter()
            .map(|block| HoverBlock {
                text: block.text,
                kind: if let Some(language) = block.language {
                    HoverBlockKind::Code { language }
                } else if block.is_markdown {
                    HoverBlockKind::Markdown
                } else {
                    HoverBlockKind::PlainText
                },
            })
            .collect();
        if contents.is_empty() {
            return Ok(None);
        }

        let language = buffer.read_with(&cx, |buffer, _| buffer.language().cloned())?;
        let range = if let (Some(start), Some(end)) = (message.start, message.end) {
            language::proto::deserialize_anchor(start)
                .and_then(|start| language::proto::deserialize_anchor(end).map(|end| start..end))
        } else {
            None
        };
        if let Some(range) = range.as_ref() {
            buffer
                .update(&mut cx, |buffer, _| {
                    buffer.wait_for_anchors([range.start, range.end])
                })?
                .await?;
        }

        Ok(Some(Hover {
            contents,
            range,
            language,
        }))
    }

    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

impl GetCompletions {
    pub fn can_resolve_completions(capabilities: &lsp::ServerCapabilities) -> bool {
        capabilities
            .completion_provider
            .as_ref()
            .and_then(|options| options.resolve_provider)
            .unwrap_or(false)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetCompletions {
    type Response = CoreCompletionResponse;
    type LspRequest = lsp::request::Completion;
    type ProtoRequest = proto::GetCompletions;

    fn display_name(&self) -> &str {
        "Get completion"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .completion_provider
            .is_some()
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::CompletionParams> {
        Ok(lsp::CompletionParams {
            text_document_position: make_lsp_text_document_position(path, self.position)?,
            context: Some(self.context.clone()),
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        completions: Option<lsp::CompletionResponse>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        mut cx: AsyncApp,
    ) -> Result<Self::Response> {
        let mut response_list = None;
        let (mut completions, mut is_incomplete) = if let Some(completions) = completions {
            match completions {
                lsp::CompletionResponse::Array(completions) => (completions, false),
                lsp::CompletionResponse::List(mut list) => {
                    let is_incomplete = list.is_incomplete;
                    let items = std::mem::take(&mut list.items);
                    response_list = Some(list);
                    (items, is_incomplete)
                }
            }
        } else {
            (Vec::new(), false)
        };

        let unfiltered_completions_count = completions.len();

        let language_server_adapter = lsp_store
            .read_with(&cx, |lsp_store, _| {
                lsp_store.language_server_adapter_for_id(server_id)
            })?
            .with_context(|| format!("no language server with id {server_id}"))?;

        let lsp_defaults = response_list
            .as_ref()
            .and_then(|list| list.item_defaults.clone())
            .map(Arc::new);

        let mut completion_edits = Vec::new();
        buffer.update(&mut cx, |buffer, _cx| {
            let snapshot = buffer.snapshot();
            let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);

            let mut range_for_token = None;
            completions.retain(|lsp_completion| {
                let lsp_edit = lsp_completion.text_edit.clone().or_else(|| {
                    let default_text_edit = lsp_defaults.as_deref()?.edit_range.as_ref()?;
                    let new_text = lsp_completion
                        .text_edit_text
                        .as_ref()
                        .unwrap_or(&lsp_completion.label)
                        .clone();
                    match default_text_edit {
                        CompletionListItemDefaultsEditRange::Range(range) => {
                            Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
                                range: *range,
                                new_text,
                            }))
                        }
                        CompletionListItemDefaultsEditRange::InsertAndReplace {
                            insert,
                            replace,
                        } => Some(lsp::CompletionTextEdit::InsertAndReplace(
                            lsp::InsertReplaceEdit {
                                new_text,
                                insert: *insert,
                                replace: *replace,
                            },
                        )),
                    }
                });

                let edit = match lsp_edit {
                    // If the language server provides a range to overwrite, then
                    // check that the range is valid.
                    Some(completion_text_edit) => {
                        match parse_completion_text_edit(&completion_text_edit, &snapshot) {
                            Some(edit) => edit,
                            None => return false,
                        }
                    }
                    // If the language server does not provide a range, then infer
                    // the range based on the syntax tree.
                    None => {
                        if self.position != clipped_position {
                            log::info!("completion out of expected range ");
                            return false;
                        }

                        let default_edit_range = lsp_defaults.as_ref().and_then(|lsp_defaults| {
                            lsp_defaults
                                .edit_range
                                .as_ref()
                                .and_then(|range| match range {
                                    CompletionListItemDefaultsEditRange::Range(r) => Some(r),
                                    _ => None,
                                })
                        });

                        let range = if let Some(range) = default_edit_range {
                            let range = range_from_lsp(*range);
                            let start = snapshot.clip_point_utf16(range.start, Bias::Left);
                            let end = snapshot.clip_point_utf16(range.end, Bias::Left);
                            if start != range.start.0 || end != range.end.0 {
                                log::info!("completion out of expected range");
                                return false;
                            }

                            snapshot.anchor_before(start)..snapshot.anchor_after(end)
                        } else {
                            range_for_token
                                .get_or_insert_with(|| {
                                    let offset = self.position.to_offset(&snapshot);
                                    let (range, kind) = snapshot.surrounding_word(
                                        offset,
                                        Some(CharScopeContext::Completion),
                                    );
                                    let range = if kind == Some(CharKind::Word) {
                                        range
                                    } else {
                                        offset..offset
                                    };

                                    snapshot.anchor_before(range.start)
                                        ..snapshot.anchor_after(range.end)
                                })
                                .clone()
                        };

                        // We already know text_edit is None here
                        let text = lsp_completion
                            .insert_text
                            .as_ref()
                            .unwrap_or(&lsp_completion.label)
                            .clone();

                        ParsedCompletionEdit {
                            replace_range: range,
                            insert_range: None,
                            new_text: text,
                        }
                    }
                };

                completion_edits.push(edit);
                true
            });
        })?;

        // If completions were filtered out due to errors that may be transient, mark the result
        // incomplete so that it is re-queried.
        if unfiltered_completions_count != completions.len() {
            is_incomplete = true;
        }

        language_server_adapter
            .process_completions(&mut completions)
            .await;

        let completions = completions
            .into_iter()
            .zip(completion_edits)
            .map(|(mut lsp_completion, mut edit)| {
                LineEnding::normalize(&mut edit.new_text);
                if lsp_completion.data.is_none()
                    && let Some(default_data) = lsp_defaults
                        .as_ref()
                        .and_then(|item_defaults| item_defaults.data.clone())
                {
                    // Servers (e.g. JDTLS) prefer unchanged completions, when resolving the items later,
                    // so we do not insert the defaults here, but `data` is needed for resolving, so this is an exception.
                    lsp_completion.data = Some(default_data);
                }
                CoreCompletion {
                    replace_range: edit.replace_range,
                    new_text: edit.new_text,
                    source: CompletionSource::Lsp {
                        insert_range: edit.insert_range,
                        server_id,
                        lsp_completion: Box::new(lsp_completion),
                        lsp_defaults: lsp_defaults.clone(),
                        resolved: false,
                    },
                }
            })
            .collect();

        Ok(CoreCompletionResponse {
            completions,
            is_incomplete,
        })
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
        let anchor = buffer.anchor_after(self.position);
        proto::GetCompletions {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(&anchor)),
            version: serialize_version(&buffer.version()),
            server_id: self.server_id.map(|id| id.to_proto()),
        }
    }

    async fn from_proto(
        message: proto::GetCompletions,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let version = deserialize_version(&message.version);
        buffer
            .update(&mut cx, |buffer, _| buffer.wait_for_version(version))?
            .await?;
        let position = message
            .position
            .and_then(language::proto::deserialize_anchor)
            .map(|p| {
                buffer.read_with(&cx, |buffer, _| {
                    buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left)
                })
            })
            .context("invalid position")??;
        Ok(Self {
            position,
            context: CompletionContext {
                trigger_kind: CompletionTriggerKind::INVOKED,
                trigger_character: None,
            },
            server_id: message
                .server_id
                .map(|id| lsp::LanguageServerId::from_proto(id)),
        })
    }

    fn response_to_proto(
        response: CoreCompletionResponse,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::GetCompletionsResponse {
        proto::GetCompletionsResponse {
            completions: response
                .completions
                .iter()
                .map(LspStore::serialize_completion)
                .collect(),
            version: serialize_version(buffer_version),
            can_reuse: !response.is_incomplete,
        }
    }

    async fn response_from_proto(
        self,
        message: proto::GetCompletionsResponse,
        _project: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self::Response> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        let completions = message
            .completions
            .into_iter()
            .map(LspStore::deserialize_completion)
            .collect::<Result<Vec<_>>>()?;

        Ok(CoreCompletionResponse {
            completions,
            is_incomplete: !message.can_reuse,
        })
    }

    fn buffer_id_from_proto(message: &proto::GetCompletions) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

pub struct ParsedCompletionEdit {
    pub replace_range: Range<Anchor>,
    pub insert_range: Option<Range<Anchor>>,
    pub new_text: String,
}

pub(crate) fn parse_completion_text_edit(
    edit: &lsp::CompletionTextEdit,
    snapshot: &BufferSnapshot,
) -> Option<ParsedCompletionEdit> {
    let (replace_range, insert_range, new_text) = match edit {
        lsp::CompletionTextEdit::Edit(edit) => (edit.range, None, &edit.new_text),
        lsp::CompletionTextEdit::InsertAndReplace(edit) => {
            (edit.replace, Some(edit.insert), &edit.new_text)
        }
    };

    let replace_range = {
        let range = range_from_lsp(replace_range);
        let start = snapshot.clip_point_utf16(range.start, Bias::Left);
        let end = snapshot.clip_point_utf16(range.end, Bias::Left);
        if start != range.start.0 || end != range.end.0 {
            log::info!(
                "completion out of expected range, start: {start:?}, end: {end:?}, range: {range:?}"
            );
            return None;
        }
        snapshot.anchor_before(start)..snapshot.anchor_after(end)
    };

    let insert_range = match insert_range {
        None => None,
        Some(insert_range) => {
            let range = range_from_lsp(insert_range);
            let start = snapshot.clip_point_utf16(range.start, Bias::Left);
            let end = snapshot.clip_point_utf16(range.end, Bias::Left);
            if start != range.start.0 || end != range.end.0 {
                log::info!("completion (insert) out of expected range");
                return None;
            }
            Some(snapshot.anchor_before(start)..snapshot.anchor_after(end))
        }
    };

    Some(ParsedCompletionEdit {
        insert_range,
        replace_range,
        new_text: new_text.clone(),
    })
}

#[async_trait(?Send)]
impl LspCommand for GetCodeActions {
    type Response = Vec<CodeAction>;
    type LspRequest = lsp::request::CodeActionRequest;
    type ProtoRequest = proto::GetCodeActions;

    fn display_name(&self) -> &str {
        "Get code actions"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        match &capabilities.server_capabilities.code_action_provider {
            None => false,
            Some(lsp::CodeActionProviderCapability::Simple(false)) => false,
            _ => {
                // If we do know that we want specific code actions AND we know that
                // the server only supports specific code actions, then we want to filter
                // down to the ones that are supported.
                if let Some((requested, supported)) = self
                    .kinds
                    .as_ref()
                    .zip(Self::supported_code_action_kinds(capabilities))
                {
                    let server_supported = supported.into_iter().collect::<HashSet<_>>();
                    requested.iter().any(|kind| server_supported.contains(kind))
                } else {
                    true
                }
            }
        }
    }

    fn to_lsp(
        &self,
        path: &Path,
        buffer: &Buffer,
        language_server: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::CodeActionParams> {
        let mut relevant_diagnostics = Vec::new();
        for entry in buffer
            .snapshot()
            .diagnostics_in_range::<_, language::PointUtf16>(self.range.clone(), false)
        {
            relevant_diagnostics.push(entry.to_lsp_diagnostic_stub()?);
        }

        let supported =
            Self::supported_code_action_kinds(language_server.adapter_server_capabilities());

        let only = if let Some(requested) = &self.kinds {
            if let Some(supported_kinds) = supported {
                let server_supported = supported_kinds.into_iter().collect::<HashSet<_>>();

                let filtered = requested
                    .iter()
                    .filter(|kind| server_supported.contains(kind))
                    .cloned()
                    .collect();
                Some(filtered)
            } else {
                Some(requested.clone())
            }
        } else {
            supported
        };

        Ok(lsp::CodeActionParams {
            text_document: make_text_document_identifier(path)?,
            range: range_to_lsp(self.range.to_point_utf16(buffer))?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
            context: lsp::CodeActionContext {
                diagnostics: relevant_diagnostics,
                only,
                ..lsp::CodeActionContext::default()
            },
        })
    }

    async fn response_from_lsp(
        self,
        actions: Option<lsp::CodeActionResponse>,
        lsp_store: Entity<LspStore>,
        _: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<CodeAction>> {
        let requested_kinds_set = self
            .kinds
            .map(|kinds| kinds.into_iter().collect::<HashSet<_>>());

        let language_server = cx.update(|cx| {
            lsp_store
                .read(cx)
                .language_server_for_id(server_id)
                .with_context(|| {
                    format!("Missing the language server that just returned a response {server_id}")
                })
        })??;

        let server_capabilities = language_server.capabilities();
        let available_commands = server_capabilities
            .execute_command_provider
            .as_ref()
            .map(|options| options.commands.as_slice())
            .unwrap_or_default();
        Ok(actions
            .unwrap_or_default()
            .into_iter()
            .filter_map(|entry| {
                let (lsp_action, resolved) = match entry {
                    lsp::CodeActionOrCommand::CodeAction(lsp_action) => {
                        if let Some(command) = lsp_action.command.as_ref()
                            && !available_commands.contains(&command.command)
                        {
                            return None;
                        }
                        (LspAction::Action(Box::new(lsp_action)), false)
                    }
                    lsp::CodeActionOrCommand::Command(command) => {
                        if available_commands.contains(&command.command) {
                            (LspAction::Command(command), true)
                        } else {
                            return None;
                        }
                    }
                };

                if let Some((requested_kinds, kind)) =
                    requested_kinds_set.as_ref().zip(lsp_action.action_kind())
                    && !requested_kinds.contains(&kind)
                {
                    return None;
                }

                Some(CodeAction {
                    server_id,
                    range: self.range.clone(),
                    lsp_action,
                    resolved,
                })
            })
            .collect())
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeActions {
        proto::GetCodeActions {
            project_id,
            buffer_id: buffer.remote_id().into(),
            start: Some(language::proto::serialize_anchor(&self.range.start)),
            end: Some(language::proto::serialize_anchor(&self.range.end)),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetCodeActions,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let start = message
            .start
            .and_then(language::proto::deserialize_anchor)
            .context("invalid start")?;
        let end = message
            .end
            .and_then(language::proto::deserialize_anchor)
            .context("invalid end")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        Ok(Self {
            range: start..end,
            kinds: None,
        })
    }

    fn response_to_proto(
        code_actions: Vec<CodeAction>,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::GetCodeActionsResponse {
        proto::GetCodeActionsResponse {
            actions: code_actions
                .iter()
                .map(LspStore::serialize_code_action)
                .collect(),
            version: serialize_version(buffer_version),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::GetCodeActionsResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Vec<CodeAction>> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        message
            .actions
            .into_iter()
            .map(LspStore::deserialize_code_action)
            .collect()
    }

    fn buffer_id_from_proto(message: &proto::GetCodeActions) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

impl GetCodeActions {
    fn supported_code_action_kinds(
        capabilities: AdapterServerCapabilities,
    ) -> Option<Vec<CodeActionKind>> {
        match capabilities.server_capabilities.code_action_provider {
            Some(lsp::CodeActionProviderCapability::Options(CodeActionOptions {
                code_action_kinds: Some(supported_action_kinds),
                ..
            })) => Some(supported_action_kinds),
            _ => capabilities.code_action_kinds,
        }
    }

    pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool {
        capabilities
            .code_action_provider
            .as_ref()
            .and_then(|options| match options {
                lsp::CodeActionProviderCapability::Simple(_is_supported) => None,
                lsp::CodeActionProviderCapability::Options(options) => options.resolve_provider,
            })
            .unwrap_or(false)
    }
}

impl OnTypeFormatting {
    pub fn supports_on_type_formatting(trigger: &str, capabilities: &ServerCapabilities) -> bool {
        let Some(on_type_formatting_options) = &capabilities.document_on_type_formatting_provider
        else {
            return false;
        };
        on_type_formatting_options
            .first_trigger_character
            .contains(trigger)
            || on_type_formatting_options
                .more_trigger_character
                .iter()
                .flatten()
                .any(|chars| chars.contains(trigger))
    }
}

#[async_trait(?Send)]
impl LspCommand for OnTypeFormatting {
    type Response = Option<Transaction>;
    type LspRequest = lsp::request::OnTypeFormatting;
    type ProtoRequest = proto::OnTypeFormatting;

    fn display_name(&self) -> &str {
        "Formatting on typing"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        Self::supports_on_type_formatting(&self.trigger, &capabilities.server_capabilities)
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::DocumentOnTypeFormattingParams> {
        Ok(lsp::DocumentOnTypeFormattingParams {
            text_document_position: make_lsp_text_document_position(path, self.position)?,
            ch: self.trigger.clone(),
            options: self.options.clone(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<Vec<lsp::TextEdit>>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        mut cx: AsyncApp,
    ) -> Result<Option<Transaction>> {
        if let Some(edits) = message {
            let (lsp_adapter, lsp_server) =
                language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
            LocalLspStore::deserialize_text_edits(
                lsp_store,
                buffer,
                edits,
                self.push_to_history,
                lsp_adapter,
                lsp_server,
                &mut cx,
            )
            .await
        } else {
            Ok(None)
        }
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::OnTypeFormatting {
        proto::OnTypeFormatting {
            project_id,
            buffer_id: buffer.remote_id().into(),
            position: Some(language::proto::serialize_anchor(
                &buffer.anchor_before(self.position),
            )),
            trigger: self.trigger.clone(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::OnTypeFormatting,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message
            .position
            .and_then(deserialize_anchor)
            .context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        let options = buffer.update(&mut cx, |buffer, cx| {
            lsp_formatting_options(
                language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx).as_ref(),
            )
        })?;

        Ok(Self {
            position: buffer.read_with(&cx, |buffer, _| position.to_point_utf16(buffer))?,
            trigger: message.trigger.clone(),
            options,
            push_to_history: false,
        })
    }

    fn response_to_proto(
        response: Option<Transaction>,
        _: &mut LspStore,
        _: PeerId,
        _: &clock::Global,
        _: &mut App,
    ) -> proto::OnTypeFormattingResponse {
        proto::OnTypeFormattingResponse {
            transaction: response
                .map(|transaction| language::proto::serialize_transaction(&transaction)),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::OnTypeFormattingResponse,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Option<Transaction>> {
        let Some(transaction) = message.transaction else {
            return Ok(None);
        };
        Ok(Some(language::proto::deserialize_transaction(transaction)?))
    }

    fn buffer_id_from_proto(message: &proto::OnTypeFormatting) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

impl InlayHints {
    pub async fn lsp_to_project_hint(
        lsp_hint: lsp::InlayHint,
        buffer_handle: &Entity<Buffer>,
        server_id: LanguageServerId,
        resolve_state: ResolveState,
        force_no_type_left_padding: bool,
        cx: &mut AsyncApp,
    ) -> anyhow::Result<InlayHint> {
        let kind = lsp_hint.kind.and_then(|kind| match kind {
            lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type),
            lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter),
            _ => None,
        });

        let position = buffer_handle.read_with(cx, |buffer, _| {
            let position = buffer.clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left);
            if kind == Some(InlayHintKind::Parameter) {
                buffer.anchor_before(position)
            } else {
                buffer.anchor_after(position)
            }
        })?;
        let label = Self::lsp_inlay_label_to_project(lsp_hint.label, server_id)
            .await
            .context("lsp to project inlay hint conversion")?;
        let padding_left = if force_no_type_left_padding && kind == Some(InlayHintKind::Type) {
            false
        } else {
            lsp_hint.padding_left.unwrap_or(false)
        };

        Ok(InlayHint {
            position,
            padding_left,
            padding_right: lsp_hint.padding_right.unwrap_or(false),
            label,
            kind,
            tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip {
                lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s),
                lsp::InlayHintTooltip::MarkupContent(markup_content) => {
                    InlayHintTooltip::MarkupContent(MarkupContent {
                        kind: match markup_content.kind {
                            lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
                            lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
                        },
                        value: markup_content.value,
                    })
                }
            }),
            resolve_state,
        })
    }

    async fn lsp_inlay_label_to_project(
        lsp_label: lsp::InlayHintLabel,
        server_id: LanguageServerId,
    ) -> anyhow::Result<InlayHintLabel> {
        let label = match lsp_label {
            lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s),
            lsp::InlayHintLabel::LabelParts(lsp_parts) => {
                let mut parts = Vec::with_capacity(lsp_parts.len());
                for lsp_part in lsp_parts {
                    parts.push(InlayHintLabelPart {
                        value: lsp_part.value,
                        tooltip: lsp_part.tooltip.map(|tooltip| match tooltip {
                            lsp::InlayHintLabelPartTooltip::String(s) => {
                                InlayHintLabelPartTooltip::String(s)
                            }
                            lsp::InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
                                InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
                                    kind: match markup_content.kind {
                                        lsp::MarkupKind::PlainText => HoverBlockKind::PlainText,
                                        lsp::MarkupKind::Markdown => HoverBlockKind::Markdown,
                                    },
                                    value: markup_content.value,
                                })
                            }
                        }),
                        location: Some(server_id).zip(lsp_part.location),
                    });
                }
                InlayHintLabel::LabelParts(parts)
            }
        };

        Ok(label)
    }

    pub fn project_to_proto_hint(response_hint: InlayHint) -> proto::InlayHint {
        let (state, lsp_resolve_state) = match response_hint.resolve_state {
            ResolveState::Resolved => (0, None),
            ResolveState::CanResolve(server_id, resolve_data) => (
                1,
                Some(proto::resolve_state::LspResolveState {
                    server_id: server_id.0 as u64,
                    value: resolve_data.map(|json_data| {
                        serde_json::to_string(&json_data)
                            .expect("failed to serialize resolve json data")
                    }),
                }),
            ),
            ResolveState::Resolving => (2, None),
        };
        let resolve_state = Some(proto::ResolveState {
            state,
            lsp_resolve_state,
        });
        proto::InlayHint {
            position: Some(language::proto::serialize_anchor(&response_hint.position)),
            padding_left: response_hint.padding_left,
            padding_right: response_hint.padding_right,
            label: Some(proto::InlayHintLabel {
                label: Some(match response_hint.label {
                    InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s),
                    InlayHintLabel::LabelParts(label_parts) => {
                        proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts {
                            parts: label_parts.into_iter().map(|label_part| {
                                let location_url = label_part.location.as_ref().map(|(_, location)| location.uri.to_string());
                                let location_range_start = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.start).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
                                let location_range_end = label_part.location.as_ref().map(|(_, location)| point_from_lsp(location.range.end).0).map(|point| proto::PointUtf16 { row: point.row, column: point.column });
                                proto::InlayHintLabelPart {
                                value: label_part.value,
                                tooltip: label_part.tooltip.map(|tooltip| {
                                    let proto_tooltip = match tooltip {
                                        InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s),
                                        InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent {
                                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
                                            value: markup_content.value,
                                        }),
                                    };
                                    proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)}
                                }),
                                location_url,
                                location_range_start,
                                location_range_end,
                                language_server_id: label_part.location.as_ref().map(|(server_id, _)| server_id.0 as u64),
                            }}).collect()
                        })
                    }
                }),
            }),
            kind: response_hint.kind.map(|kind| kind.name().to_string()),
            tooltip: response_hint.tooltip.map(|response_tooltip| {
                let proto_tooltip = match response_tooltip {
                    InlayHintTooltip::String(s) => proto::inlay_hint_tooltip::Content::Value(s),
                    InlayHintTooltip::MarkupContent(markup_content) => {
                        proto::inlay_hint_tooltip::Content::MarkupContent(proto::MarkupContent {
                            is_markdown: markup_content.kind == HoverBlockKind::Markdown,
                            value: markup_content.value,
                        })
                    }
                };
                proto::InlayHintTooltip {
                    content: Some(proto_tooltip),
                }
            }),
            resolve_state,
        }
    }

    pub fn proto_to_project_hint(message_hint: proto::InlayHint) -> anyhow::Result<InlayHint> {
        let resolve_state = message_hint.resolve_state.as_ref().unwrap_or_else(|| {
            panic!("incorrect proto inlay hint message: no resolve state in hint {message_hint:?}",)
        });
        let resolve_state_data = resolve_state
            .lsp_resolve_state.as_ref()
            .map(|lsp_resolve_state| {
                let value = lsp_resolve_state.value.as_deref().map(|value| {
                    serde_json::from_str::<Option<lsp::LSPAny>>(value)
                        .with_context(|| format!("incorrect proto inlay hint message: non-json resolve state {lsp_resolve_state:?}"))
                }).transpose()?.flatten();
                anyhow::Ok((LanguageServerId(lsp_resolve_state.server_id as usize), value))
            })
            .transpose()?;
        let resolve_state = match resolve_state.state {
            0 => ResolveState::Resolved,
            1 => {
                let (server_id, lsp_resolve_state) = resolve_state_data.with_context(|| {
                    format!(
                        "No lsp resolve data for the hint that can be resolved: {message_hint:?}"
                    )
                })?;
                ResolveState::CanResolve(server_id, lsp_resolve_state)
            }
            2 => ResolveState::Resolving,
            invalid => {
                anyhow::bail!("Unexpected resolve state {invalid} for hint {message_hint:?}")
            }
        };
        Ok(InlayHint {
            position: message_hint
                .position
                .and_then(language::proto::deserialize_anchor)
                .context("invalid position")?,
            label: match message_hint
                .label
                .and_then(|label| label.label)
                .context("missing label")?
            {
                proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s),
                proto::inlay_hint_label::Label::LabelParts(parts) => {
                    let mut label_parts = Vec::new();
                    for part in parts.parts {
                        label_parts.push(InlayHintLabelPart {
                            value: part.value,
                            tooltip: part.tooltip.map(|tooltip| match tooltip.content {
                                Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => {
                                    InlayHintLabelPartTooltip::String(s)
                                }
                                Some(
                                    proto::inlay_hint_label_part_tooltip::Content::MarkupContent(
                                        markup_content,
                                    ),
                                ) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent {
                                    kind: if markup_content.is_markdown {
                                        HoverBlockKind::Markdown
                                    } else {
                                        HoverBlockKind::PlainText
                                    },
                                    value: markup_content.value,
                                }),
                                None => InlayHintLabelPartTooltip::String(String::new()),
                            }),
                            location: {
                                match part
                                    .location_url
                                    .zip(
                                        part.location_range_start.and_then(|start| {
                                            Some(start..part.location_range_end?)
                                        }),
                                    )
                                    .zip(part.language_server_id)
                                {
                                    Some(((uri, range), server_id)) => Some((
                                        LanguageServerId(server_id as usize),
                                        lsp::Location {
                                            uri: lsp::Uri::from_str(&uri)
                                                .context("invalid uri in hint part {part:?}")?,
                                            range: lsp::Range::new(
                                                point_to_lsp(PointUtf16::new(
                                                    range.start.row,
                                                    range.start.column,
                                                )),
                                                point_to_lsp(PointUtf16::new(
                                                    range.end.row,
                                                    range.end.column,
                                                )),
                                            ),
                                        },
                                    )),
                                    None => None,
                                }
                            },
                        });
                    }

                    InlayHintLabel::LabelParts(label_parts)
                }
            },
            padding_left: message_hint.padding_left,
            padding_right: message_hint.padding_right,
            kind: message_hint
                .kind
                .as_deref()
                .and_then(InlayHintKind::from_name),
            tooltip: message_hint.tooltip.and_then(|tooltip| {
                Some(match tooltip.content? {
                    proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s),
                    proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => {
                        InlayHintTooltip::MarkupContent(MarkupContent {
                            kind: if markup_content.is_markdown {
                                HoverBlockKind::Markdown
                            } else {
                                HoverBlockKind::PlainText
                            },
                            value: markup_content.value,
                        })
                    }
                })
            }),
            resolve_state,
        })
    }

    pub fn project_to_lsp_hint(hint: InlayHint, snapshot: &BufferSnapshot) -> lsp::InlayHint {
        lsp::InlayHint {
            position: point_to_lsp(hint.position.to_point_utf16(snapshot)),
            kind: hint.kind.map(|kind| match kind {
                InlayHintKind::Type => lsp::InlayHintKind::TYPE,
                InlayHintKind::Parameter => lsp::InlayHintKind::PARAMETER,
            }),
            text_edits: None,
            tooltip: hint.tooltip.and_then(|tooltip| {
                Some(match tooltip {
                    InlayHintTooltip::String(s) => lsp::InlayHintTooltip::String(s),
                    InlayHintTooltip::MarkupContent(markup_content) => {
                        lsp::InlayHintTooltip::MarkupContent(lsp::MarkupContent {
                            kind: match markup_content.kind {
                                HoverBlockKind::PlainText => lsp::MarkupKind::PlainText,
                                HoverBlockKind::Markdown => lsp::MarkupKind::Markdown,
                                HoverBlockKind::Code { .. } => return None,
                            },
                            value: markup_content.value,
                        })
                    }
                })
            }),
            label: match hint.label {
                InlayHintLabel::String(s) => lsp::InlayHintLabel::String(s),
                InlayHintLabel::LabelParts(label_parts) => lsp::InlayHintLabel::LabelParts(
                    label_parts
                        .into_iter()
                        .map(|part| lsp::InlayHintLabelPart {
                            value: part.value,
                            tooltip: part.tooltip.and_then(|tooltip| {
                                Some(match tooltip {
                                    InlayHintLabelPartTooltip::String(s) => {
                                        lsp::InlayHintLabelPartTooltip::String(s)
                                    }
                                    InlayHintLabelPartTooltip::MarkupContent(markup_content) => {
                                        lsp::InlayHintLabelPartTooltip::MarkupContent(
                                            lsp::MarkupContent {
                                                kind: match markup_content.kind {
                                                    HoverBlockKind::PlainText => {
                                                        lsp::MarkupKind::PlainText
                                                    }
                                                    HoverBlockKind::Markdown => {
                                                        lsp::MarkupKind::Markdown
                                                    }
                                                    HoverBlockKind::Code { .. } => return None,
                                                },
                                                value: markup_content.value,
                                            },
                                        )
                                    }
                                })
                            }),
                            location: part.location.map(|(_, location)| location),
                            command: None,
                        })
                        .collect(),
                ),
            },
            padding_left: Some(hint.padding_left),
            padding_right: Some(hint.padding_right),
            data: match hint.resolve_state {
                ResolveState::CanResolve(_, data) => data,
                ResolveState::Resolving | ResolveState::Resolved => None,
            },
        }
    }

    pub fn can_resolve_inlays(capabilities: &ServerCapabilities) -> bool {
        capabilities
            .inlay_hint_provider
            .as_ref()
            .and_then(|options| match options {
                OneOf::Left(_is_supported) => None,
                OneOf::Right(capabilities) => match capabilities {
                    lsp::InlayHintServerCapabilities::Options(o) => o.resolve_provider,
                    lsp::InlayHintServerCapabilities::RegistrationOptions(o) => {
                        o.inlay_hint_options.resolve_provider
                    }
                },
            })
            .unwrap_or(false)
    }

    pub fn check_capabilities(capabilities: &ServerCapabilities) -> bool {
        capabilities
            .inlay_hint_provider
            .as_ref()
            .is_some_and(|inlay_hint_provider| match inlay_hint_provider {
                lsp::OneOf::Left(enabled) => *enabled,
                lsp::OneOf::Right(_) => true,
            })
    }
}

#[async_trait(?Send)]
impl LspCommand for InlayHints {
    type Response = Vec<InlayHint>;
    type LspRequest = lsp::InlayHintRequest;
    type ProtoRequest = proto::InlayHints;

    fn display_name(&self) -> &str {
        "Inlay hints"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        Self::check_capabilities(&capabilities.server_capabilities)
    }

    fn to_lsp(
        &self,
        path: &Path,
        buffer: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::InlayHintParams> {
        Ok(lsp::InlayHintParams {
            text_document: lsp::TextDocumentIdentifier {
                uri: file_path_to_lsp_url(path)?,
            },
            range: range_to_lsp(self.range.to_point_utf16(buffer))?,
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<Vec<lsp::InlayHint>>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        mut cx: AsyncApp,
    ) -> anyhow::Result<Vec<InlayHint>> {
        let (lsp_adapter, lsp_server) =
            language_server_for_buffer(&lsp_store, &buffer, server_id, &mut cx)?;
        // `typescript-language-server` adds padding to the left for type hints, turning
        // `const foo: boolean` into `const foo : boolean` which looks odd.
        // `rust-analyzer` does not have the padding for this case, and we have to accommodate both.
        //
        // We could trim the whole string, but being pessimistic on par with the situation above,
        // there might be a hint with multiple whitespaces at the end(s) which we need to display properly.
        // Hence let's use a heuristic first to handle the most awkward case and look for more.
        let force_no_type_left_padding =
            lsp_adapter.name.0.as_ref() == "typescript-language-server";

        let hints = message.unwrap_or_default().into_iter().map(|lsp_hint| {
            let resolve_state = if InlayHints::can_resolve_inlays(&lsp_server.capabilities()) {
                ResolveState::CanResolve(lsp_server.server_id(), lsp_hint.data.clone())
            } else {
                ResolveState::Resolved
            };

            let buffer = buffer.clone();
            cx.spawn(async move |cx| {
                InlayHints::lsp_to_project_hint(
                    lsp_hint,
                    &buffer,
                    server_id,
                    resolve_state,
                    force_no_type_left_padding,
                    cx,
                )
                .await
            })
        });
        future::join_all(hints)
            .await
            .into_iter()
            .collect::<anyhow::Result<_>>()
            .context("lsp to project inlay hints conversion")
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints {
        proto::InlayHints {
            project_id,
            buffer_id: buffer.remote_id().into(),
            start: Some(language::proto::serialize_anchor(&self.range.start)),
            end: Some(language::proto::serialize_anchor(&self.range.end)),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::InlayHints,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let start = message
            .start
            .and_then(language::proto::deserialize_anchor)
            .context("invalid start")?;
        let end = message
            .end
            .and_then(language::proto::deserialize_anchor)
            .context("invalid end")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        Ok(Self { range: start..end })
    }

    fn response_to_proto(
        response: Vec<InlayHint>,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::InlayHintsResponse {
        proto::InlayHintsResponse {
            hints: response
                .into_iter()
                .map(InlayHints::project_to_proto_hint)
                .collect(),
            version: serialize_version(buffer_version),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::InlayHintsResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> anyhow::Result<Vec<InlayHint>> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;

        let mut hints = Vec::new();
        for message_hint in message.hints {
            hints.push(InlayHints::proto_to_project_hint(message_hint)?);
        }

        Ok(hints)
    }

    fn buffer_id_from_proto(message: &proto::InlayHints) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetCodeLens {
    type Response = Vec<CodeAction>;
    type LspRequest = lsp::CodeLensRequest;
    type ProtoRequest = proto::GetCodeLens;

    fn display_name(&self) -> &str {
        "Code Lens"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        capabilities
            .server_capabilities
            .code_lens_provider
            .is_some()
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::CodeLensParams> {
        Ok(lsp::CodeLensParams {
            text_document: lsp::TextDocumentIdentifier {
                uri: file_path_to_lsp_url(path)?,
            },
            work_done_progress_params: lsp::WorkDoneProgressParams::default(),
            partial_result_params: lsp::PartialResultParams::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<Vec<lsp::CodeLens>>,
        lsp_store: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> anyhow::Result<Vec<CodeAction>> {
        let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
        let language_server = cx.update(|cx| {
            lsp_store
                .read(cx)
                .language_server_for_id(server_id)
                .with_context(|| {
                    format!("Missing the language server that just returned a response {server_id}")
                })
        })??;
        let server_capabilities = language_server.capabilities();
        let available_commands = server_capabilities
            .execute_command_provider
            .as_ref()
            .map(|options| options.commands.as_slice())
            .unwrap_or_default();
        Ok(message
            .unwrap_or_default()
            .into_iter()
            .filter(|code_lens| {
                code_lens
                    .command
                    .as_ref()
                    .is_none_or(|command| available_commands.contains(&command.command))
            })
            .map(|code_lens| {
                let code_lens_range = range_from_lsp(code_lens.range);
                let start = snapshot.clip_point_utf16(code_lens_range.start, Bias::Left);
                let end = snapshot.clip_point_utf16(code_lens_range.end, Bias::Right);
                let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
                CodeAction {
                    server_id,
                    range,
                    lsp_action: LspAction::CodeLens(code_lens),
                    resolved: false,
                }
            })
            .collect())
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCodeLens {
        proto::GetCodeLens {
            project_id,
            buffer_id: buffer.remote_id().into(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::GetCodeLens,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        Ok(Self)
    }

    fn response_to_proto(
        response: Vec<CodeAction>,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::GetCodeLensResponse {
        proto::GetCodeLensResponse {
            lens_actions: response
                .iter()
                .map(LspStore::serialize_code_action)
                .collect(),
            version: serialize_version(buffer_version),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::GetCodeLensResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> anyhow::Result<Vec<CodeAction>> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        message
            .lens_actions
            .into_iter()
            .map(LspStore::deserialize_code_action)
            .collect::<Result<Vec<_>>>()
            .context("deserializing proto code lens response")
    }

    fn buffer_id_from_proto(message: &proto::GetCodeLens) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

impl LinkedEditingRange {
    pub fn check_server_capabilities(capabilities: ServerCapabilities) -> bool {
        let Some(linked_editing_options) = capabilities.linked_editing_range_provider else {
            return false;
        };
        if let LinkedEditingRangeServerCapabilities::Simple(false) = linked_editing_options {
            return false;
        }
        true
    }
}

#[async_trait(?Send)]
impl LspCommand for LinkedEditingRange {
    type Response = Vec<Range<Anchor>>;
    type LspRequest = lsp::request::LinkedEditingRange;
    type ProtoRequest = proto::LinkedEditingRange;

    fn display_name(&self) -> &str {
        "Linked editing range"
    }

    fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool {
        Self::check_server_capabilities(capabilities.server_capabilities)
    }

    fn to_lsp(
        &self,
        path: &Path,
        buffer: &Buffer,
        _server: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::LinkedEditingRangeParams> {
        let position = self.position.to_point_utf16(&buffer.snapshot());
        Ok(lsp::LinkedEditingRangeParams {
            text_document_position_params: make_lsp_text_document_position(path, position)?,
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Option<lsp::LinkedEditingRanges>,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        _server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Vec<Range<Anchor>>> {
        if let Some(lsp::LinkedEditingRanges { mut ranges, .. }) = message {
            ranges.sort_by_key(|range| range.start);

            buffer.read_with(&cx, |buffer, _| {
                ranges
                    .into_iter()
                    .map(|range| {
                        let start =
                            buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left);
                        let end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left);
                        buffer.anchor_before(start)..buffer.anchor_after(end)
                    })
                    .collect()
            })
        } else {
            Ok(vec![])
        }
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LinkedEditingRange {
        proto::LinkedEditingRange {
            project_id,
            buffer_id: buffer.remote_id().to_proto(),
            position: Some(serialize_anchor(&self.position)),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        message: proto::LinkedEditingRange,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Self> {
        let position = message.position.context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        let position = deserialize_anchor(position).context("invalid position")?;
        buffer
            .update(&mut cx, |buffer, _| buffer.wait_for_anchors([position]))?
            .await?;
        Ok(Self { position })
    }

    fn response_to_proto(
        response: Vec<Range<Anchor>>,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::LinkedEditingRangeResponse {
        proto::LinkedEditingRangeResponse {
            items: response
                .into_iter()
                .map(|range| proto::AnchorRange {
                    start: Some(serialize_anchor(&range.start)),
                    end: Some(serialize_anchor(&range.end)),
                })
                .collect(),
            version: serialize_version(buffer_version),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::LinkedEditingRangeResponse,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        mut cx: AsyncApp,
    ) -> Result<Vec<Range<Anchor>>> {
        buffer
            .update(&mut cx, |buffer, _| {
                buffer.wait_for_version(deserialize_version(&message.version))
            })?
            .await?;
        let items: Vec<Range<Anchor>> = message
            .items
            .into_iter()
            .filter_map(|range| {
                let start = deserialize_anchor(range.start?)?;
                let end = deserialize_anchor(range.end?)?;
                Some(start..end)
            })
            .collect();
        for range in &items {
            buffer
                .update(&mut cx, |buffer, _| {
                    buffer.wait_for_anchors([range.start, range.end])
                })?
                .await?;
        }
        Ok(items)
    }

    fn buffer_id_from_proto(message: &proto::LinkedEditingRange) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

impl GetDocumentDiagnostics {
    pub fn diagnostics_from_proto(
        response: proto::GetDocumentDiagnosticsResponse,
    ) -> Vec<LspPullDiagnostics> {
        response
            .pulled_diagnostics
            .into_iter()
            .filter_map(|diagnostics| {
                Some(LspPullDiagnostics::Response {
                    server_id: LanguageServerId::from_proto(diagnostics.server_id),
                    uri: lsp::Uri::from_str(diagnostics.uri.as_str()).log_err()?,
                    diagnostics: if diagnostics.changed {
                        PulledDiagnostics::Unchanged {
                            result_id: diagnostics.result_id?,
                        }
                    } else {
                        PulledDiagnostics::Changed {
                            result_id: diagnostics.result_id,
                            diagnostics: diagnostics
                                .diagnostics
                                .into_iter()
                                .filter_map(|diagnostic| {
                                    GetDocumentDiagnostics::deserialize_lsp_diagnostic(diagnostic)
                                        .context("deserializing diagnostics")
                                        .log_err()
                                })
                                .collect(),
                        }
                    },
                })
            })
            .collect()
    }

    fn deserialize_lsp_diagnostic(diagnostic: proto::LspDiagnostic) -> Result<lsp::Diagnostic> {
        let start = diagnostic.start.context("invalid start range")?;
        let end = diagnostic.end.context("invalid end range")?;

        let range = Range::<PointUtf16> {
            start: PointUtf16 {
                row: start.row,
                column: start.column,
            },
            end: PointUtf16 {
                row: end.row,
                column: end.column,
            },
        };

        let data = diagnostic.data.and_then(|data| Value::from_str(&data).ok());
        let code = diagnostic.code.map(lsp::NumberOrString::String);

        let related_information = diagnostic
            .related_information
            .into_iter()
            .map(|info| {
                let start = info.location_range_start.unwrap();
                let end = info.location_range_end.unwrap();

                lsp::DiagnosticRelatedInformation {
                    location: lsp::Location {
                        range: lsp::Range {
                            start: point_to_lsp(PointUtf16::new(start.row, start.column)),
                            end: point_to_lsp(PointUtf16::new(end.row, end.column)),
                        },
                        uri: lsp::Uri::from_str(&info.location_url.unwrap()).unwrap(),
                    },
                    message: info.message,
                }
            })
            .collect::<Vec<_>>();

        let tags = diagnostic
            .tags
            .into_iter()
            .filter_map(|tag| match proto::LspDiagnosticTag::from_i32(tag) {
                Some(proto::LspDiagnosticTag::Unnecessary) => Some(lsp::DiagnosticTag::UNNECESSARY),
                Some(proto::LspDiagnosticTag::Deprecated) => Some(lsp::DiagnosticTag::DEPRECATED),
                _ => None,
            })
            .collect::<Vec<_>>();

        Ok(lsp::Diagnostic {
            range: language::range_to_lsp(range)?,
            severity: match proto::lsp_diagnostic::Severity::from_i32(diagnostic.severity).unwrap()
            {
                proto::lsp_diagnostic::Severity::Error => Some(lsp::DiagnosticSeverity::ERROR),
                proto::lsp_diagnostic::Severity::Warning => Some(lsp::DiagnosticSeverity::WARNING),
                proto::lsp_diagnostic::Severity::Information => {
                    Some(lsp::DiagnosticSeverity::INFORMATION)
                }
                proto::lsp_diagnostic::Severity::Hint => Some(lsp::DiagnosticSeverity::HINT),
                _ => None,
            },
            code,
            code_description: diagnostic
                .code_description
                .map(|code_description| CodeDescription {
                    href: Some(lsp::Uri::from_str(&code_description).unwrap()),
                }),
            related_information: Some(related_information),
            tags: Some(tags),
            source: diagnostic.source.clone(),
            message: diagnostic.message,
            data,
        })
    }

    fn serialize_lsp_diagnostic(diagnostic: lsp::Diagnostic) -> Result<proto::LspDiagnostic> {
        let range = language::range_from_lsp(diagnostic.range);
        let related_information = diagnostic
            .related_information
            .unwrap_or_default()
            .into_iter()
            .map(|related_information| {
                let location_range_start =
                    point_from_lsp(related_information.location.range.start).0;
                let location_range_end = point_from_lsp(related_information.location.range.end).0;

                Ok(proto::LspDiagnosticRelatedInformation {
                    location_url: Some(related_information.location.uri.to_string()),
                    location_range_start: Some(proto::PointUtf16 {
                        row: location_range_start.row,
                        column: location_range_start.column,
                    }),
                    location_range_end: Some(proto::PointUtf16 {
                        row: location_range_end.row,
                        column: location_range_end.column,
                    }),
                    message: related_information.message,
                })
            })
            .collect::<Result<Vec<_>>>()?;

        let tags = diagnostic
            .tags
            .unwrap_or_default()
            .into_iter()
            .map(|tag| match tag {
                lsp::DiagnosticTag::UNNECESSARY => proto::LspDiagnosticTag::Unnecessary,
                lsp::DiagnosticTag::DEPRECATED => proto::LspDiagnosticTag::Deprecated,
                _ => proto::LspDiagnosticTag::None,
            } as i32)
            .collect();

        Ok(proto::LspDiagnostic {
            start: Some(proto::PointUtf16 {
                row: range.start.0.row,
                column: range.start.0.column,
            }),
            end: Some(proto::PointUtf16 {
                row: range.end.0.row,
                column: range.end.0.column,
            }),
            severity: match diagnostic.severity {
                Some(lsp::DiagnosticSeverity::ERROR) => proto::lsp_diagnostic::Severity::Error,
                Some(lsp::DiagnosticSeverity::WARNING) => proto::lsp_diagnostic::Severity::Warning,
                Some(lsp::DiagnosticSeverity::INFORMATION) => {
                    proto::lsp_diagnostic::Severity::Information
                }
                Some(lsp::DiagnosticSeverity::HINT) => proto::lsp_diagnostic::Severity::Hint,
                _ => proto::lsp_diagnostic::Severity::None,
            } as i32,
            code: diagnostic.code.as_ref().map(|code| match code {
                lsp::NumberOrString::Number(code) => code.to_string(),
                lsp::NumberOrString::String(code) => code.clone(),
            }),
            source: diagnostic.source.clone(),
            related_information,
            tags,
            code_description: diagnostic
                .code_description
                .and_then(|desc| desc.href.map(|url| url.to_string())),
            message: diagnostic.message,
            data: diagnostic.data.as_ref().map(|data| data.to_string()),
        })
    }

    pub fn deserialize_workspace_diagnostics_report(
        report: lsp::WorkspaceDiagnosticReportResult,
        server_id: LanguageServerId,
    ) -> Vec<WorkspaceLspPullDiagnostics> {
        let mut pulled_diagnostics = HashMap::default();
        match report {
            lsp::WorkspaceDiagnosticReportResult::Report(workspace_diagnostic_report) => {
                for report in workspace_diagnostic_report.items {
                    match report {
                        lsp::WorkspaceDocumentDiagnosticReport::Full(report) => {
                            process_full_workspace_diagnostics_report(
                                &mut pulled_diagnostics,
                                server_id,
                                report,
                            )
                        }
                        lsp::WorkspaceDocumentDiagnosticReport::Unchanged(report) => {
                            process_unchanged_workspace_diagnostics_report(
                                &mut pulled_diagnostics,
                                server_id,
                                report,
                            )
                        }
                    }
                }
            }
            lsp::WorkspaceDiagnosticReportResult::Partial(
                workspace_diagnostic_report_partial_result,
            ) => {
                for report in workspace_diagnostic_report_partial_result.items {
                    match report {
                        lsp::WorkspaceDocumentDiagnosticReport::Full(report) => {
                            process_full_workspace_diagnostics_report(
                                &mut pulled_diagnostics,
                                server_id,
                                report,
                            )
                        }
                        lsp::WorkspaceDocumentDiagnosticReport::Unchanged(report) => {
                            process_unchanged_workspace_diagnostics_report(
                                &mut pulled_diagnostics,
                                server_id,
                                report,
                            )
                        }
                    }
                }
            }
        }
        pulled_diagnostics.into_values().collect()
    }
}

#[derive(Debug)]
pub struct WorkspaceLspPullDiagnostics {
    pub version: Option<i32>,
    pub diagnostics: LspPullDiagnostics,
}

fn process_full_workspace_diagnostics_report(
    diagnostics: &mut HashMap<lsp::Uri, WorkspaceLspPullDiagnostics>,
    server_id: LanguageServerId,
    report: lsp::WorkspaceFullDocumentDiagnosticReport,
) {
    let mut new_diagnostics = HashMap::default();
    process_full_diagnostics_report(
        &mut new_diagnostics,
        server_id,
        report.uri,
        report.full_document_diagnostic_report,
    );
    diagnostics.extend(new_diagnostics.into_iter().map(|(uri, diagnostics)| {
        (
            uri,
            WorkspaceLspPullDiagnostics {
                version: report.version.map(|v| v as i32),
                diagnostics,
            },
        )
    }));
}

fn process_unchanged_workspace_diagnostics_report(
    diagnostics: &mut HashMap<lsp::Uri, WorkspaceLspPullDiagnostics>,
    server_id: LanguageServerId,
    report: lsp::WorkspaceUnchangedDocumentDiagnosticReport,
) {
    let mut new_diagnostics = HashMap::default();
    process_unchanged_diagnostics_report(
        &mut new_diagnostics,
        server_id,
        report.uri,
        report.unchanged_document_diagnostic_report,
    );
    diagnostics.extend(new_diagnostics.into_iter().map(|(uri, diagnostics)| {
        (
            uri,
            WorkspaceLspPullDiagnostics {
                version: report.version.map(|v| v as i32),
                diagnostics,
            },
        )
    }));
}

#[async_trait(?Send)]
impl LspCommand for GetDocumentDiagnostics {
    type Response = Vec<LspPullDiagnostics>;
    type LspRequest = lsp::request::DocumentDiagnosticRequest;
    type ProtoRequest = proto::GetDocumentDiagnostics;

    fn display_name(&self) -> &str {
        "Get diagnostics"
    }

    fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool {
        true
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::DocumentDiagnosticParams> {
        let identifier = match &self.dynamic_caps {
            lsp::DiagnosticServerCapabilities::Options(options) => options.identifier.clone(),
            lsp::DiagnosticServerCapabilities::RegistrationOptions(options) => {
                options.diagnostic_options.identifier.clone()
            }
        };

        Ok(lsp::DocumentDiagnosticParams {
            text_document: lsp::TextDocumentIdentifier {
                uri: file_path_to_lsp_url(path)?,
            },
            identifier,
            previous_result_id: self.previous_result_id.clone(),
            partial_result_params: Default::default(),
            work_done_progress_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: lsp::DocumentDiagnosticReportResult,
        _: Entity<LspStore>,
        buffer: Entity<Buffer>,
        server_id: LanguageServerId,
        cx: AsyncApp,
    ) -> Result<Self::Response> {
        let url = buffer.read_with(&cx, |buffer, cx| {
            buffer
                .file()
                .and_then(|file| file.as_local())
                .map(|file| {
                    let abs_path = file.abs_path(cx);
                    file_path_to_lsp_url(&abs_path)
                })
                .transpose()?
                .with_context(|| format!("missing url on buffer {}", buffer.remote_id()))
        })??;

        let mut pulled_diagnostics = HashMap::default();
        match message {
            lsp::DocumentDiagnosticReportResult::Report(report) => match report {
                lsp::DocumentDiagnosticReport::Full(report) => {
                    if let Some(related_documents) = report.related_documents {
                        process_related_documents(
                            &mut pulled_diagnostics,
                            server_id,
                            related_documents,
                        );
                    }
                    process_full_diagnostics_report(
                        &mut pulled_diagnostics,
                        server_id,
                        url,
                        report.full_document_diagnostic_report,
                    );
                }
                lsp::DocumentDiagnosticReport::Unchanged(report) => {
                    if let Some(related_documents) = report.related_documents {
                        process_related_documents(
                            &mut pulled_diagnostics,
                            server_id,
                            related_documents,
                        );
                    }
                    process_unchanged_diagnostics_report(
                        &mut pulled_diagnostics,
                        server_id,
                        url,
                        report.unchanged_document_diagnostic_report,
                    );
                }
            },
            lsp::DocumentDiagnosticReportResult::Partial(report) => {
                if let Some(related_documents) = report.related_documents {
                    process_related_documents(
                        &mut pulled_diagnostics,
                        server_id,
                        related_documents,
                    );
                }
            }
        }

        Ok(pulled_diagnostics.into_values().collect())
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDocumentDiagnostics {
        proto::GetDocumentDiagnostics {
            project_id,
            buffer_id: buffer.remote_id().into(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        _: proto::GetDocumentDiagnostics,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Self> {
        anyhow::bail!(
            "proto::GetDocumentDiagnostics is not expected to be converted from proto directly, as it needs `previous_result_id` fetched first"
        )
    }

    fn response_to_proto(
        response: Self::Response,
        _: &mut LspStore,
        _: PeerId,
        _: &clock::Global,
        _: &mut App,
    ) -> proto::GetDocumentDiagnosticsResponse {
        let pulled_diagnostics = response
            .into_iter()
            .filter_map(|diagnostics| match diagnostics {
                LspPullDiagnostics::Default => None,
                LspPullDiagnostics::Response {
                    server_id,
                    uri,
                    diagnostics,
                } => {
                    let mut changed = false;
                    let (diagnostics, result_id) = match diagnostics {
                        PulledDiagnostics::Unchanged { result_id } => (Vec::new(), Some(result_id)),
                        PulledDiagnostics::Changed {
                            result_id,
                            diagnostics,
                        } => {
                            changed = true;
                            (diagnostics, result_id)
                        }
                    };
                    Some(proto::PulledDiagnostics {
                        changed,
                        result_id,
                        uri: uri.to_string(),
                        server_id: server_id.to_proto(),
                        diagnostics: diagnostics
                            .into_iter()
                            .filter_map(|diagnostic| {
                                GetDocumentDiagnostics::serialize_lsp_diagnostic(diagnostic)
                                    .context("serializing diagnostics")
                                    .log_err()
                            })
                            .collect(),
                    })
                }
            })
            .collect();

        proto::GetDocumentDiagnosticsResponse { pulled_diagnostics }
    }

    async fn response_from_proto(
        self,
        response: proto::GetDocumentDiagnosticsResponse,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Self::Response> {
        Ok(Self::diagnostics_from_proto(response))
    }

    fn buffer_id_from_proto(message: &proto::GetDocumentDiagnostics) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

#[async_trait(?Send)]
impl LspCommand for GetDocumentColor {
    type Response = Vec<DocumentColor>;
    type LspRequest = lsp::request::DocumentColor;
    type ProtoRequest = proto::GetDocumentColor;

    fn display_name(&self) -> &str {
        "Document color"
    }

    fn check_capabilities(&self, server_capabilities: AdapterServerCapabilities) -> bool {
        server_capabilities
            .server_capabilities
            .color_provider
            .as_ref()
            .is_some_and(|capability| match capability {
                lsp::ColorProviderCapability::Simple(supported) => *supported,
                lsp::ColorProviderCapability::ColorProvider(..) => true,
                lsp::ColorProviderCapability::Options(..) => true,
            })
    }

    fn to_lsp(
        &self,
        path: &Path,
        _: &Buffer,
        _: &Arc<LanguageServer>,
        _: &App,
    ) -> Result<lsp::DocumentColorParams> {
        Ok(lsp::DocumentColorParams {
            text_document: make_text_document_identifier(path)?,
            work_done_progress_params: Default::default(),
            partial_result_params: Default::default(),
        })
    }

    async fn response_from_lsp(
        self,
        message: Vec<lsp::ColorInformation>,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: LanguageServerId,
        _: AsyncApp,
    ) -> Result<Self::Response> {
        Ok(message
            .into_iter()
            .map(|color| DocumentColor {
                lsp_range: color.range,
                color: color.color,
                resolved: false,
                color_presentations: Vec::new(),
            })
            .collect())
    }

    fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest {
        proto::GetDocumentColor {
            project_id,
            buffer_id: buffer.remote_id().to_proto(),
            version: serialize_version(&buffer.version()),
        }
    }

    async fn from_proto(
        _: Self::ProtoRequest,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Self> {
        Ok(Self {})
    }

    fn response_to_proto(
        response: Self::Response,
        _: &mut LspStore,
        _: PeerId,
        buffer_version: &clock::Global,
        _: &mut App,
    ) -> proto::GetDocumentColorResponse {
        proto::GetDocumentColorResponse {
            colors: response
                .into_iter()
                .map(|color| {
                    let start = point_from_lsp(color.lsp_range.start).0;
                    let end = point_from_lsp(color.lsp_range.end).0;
                    proto::ColorInformation {
                        red: color.color.red,
                        green: color.color.green,
                        blue: color.color.blue,
                        alpha: color.color.alpha,
                        lsp_range_start: Some(proto::PointUtf16 {
                            row: start.row,
                            column: start.column,
                        }),
                        lsp_range_end: Some(proto::PointUtf16 {
                            row: end.row,
                            column: end.column,
                        }),
                    }
                })
                .collect(),
            version: serialize_version(buffer_version),
        }
    }

    async fn response_from_proto(
        self,
        message: proto::GetDocumentColorResponse,
        _: Entity<LspStore>,
        _: Entity<Buffer>,
        _: AsyncApp,
    ) -> Result<Self::Response> {
        Ok(message
            .colors
            .into_iter()
            .filter_map(|color| {
                let start = color.lsp_range_start?;
                let start = PointUtf16::new(start.row, start.column);
                let end = color.lsp_range_end?;
                let end = PointUtf16::new(end.row, end.column);
                Some(DocumentColor {
                    resolved: false,
                    color_presentations: Vec::new(),
                    lsp_range: lsp::Range {
                        start: point_to_lsp(start),
                        end: point_to_lsp(end),
                    },
                    color: lsp::Color {
                        red: color.red,
                        green: color.green,
                        blue: color.blue,
                        alpha: color.alpha,
                    },
                })
            })
            .collect())
    }

    fn buffer_id_from_proto(message: &Self::ProtoRequest) -> Result<BufferId> {
        BufferId::new(message.buffer_id)
    }
}

fn process_related_documents(
    diagnostics: &mut HashMap<lsp::Uri, LspPullDiagnostics>,
    server_id: LanguageServerId,
    documents: impl IntoIterator<Item = (lsp::Uri, lsp::DocumentDiagnosticReportKind)>,
) {
    for (url, report_kind) in documents {
        match report_kind {
            lsp::DocumentDiagnosticReportKind::Full(report) => {
                process_full_diagnostics_report(diagnostics, server_id, url, report)
            }
            lsp::DocumentDiagnosticReportKind::Unchanged(report) => {
                process_unchanged_diagnostics_report(diagnostics, server_id, url, report)
            }
        }
    }
}

fn process_unchanged_diagnostics_report(
    diagnostics: &mut HashMap<lsp::Uri, LspPullDiagnostics>,
    server_id: LanguageServerId,
    uri: lsp::Uri,
    report: lsp::UnchangedDocumentDiagnosticReport,
) {
    let result_id = report.result_id;
    match diagnostics.entry(uri.clone()) {
        hash_map::Entry::Occupied(mut o) => match o.get_mut() {
            LspPullDiagnostics::Default => {
                o.insert(LspPullDiagnostics::Response {
                    server_id,
                    uri,
                    diagnostics: PulledDiagnostics::Unchanged { result_id },
                });
            }
            LspPullDiagnostics::Response {
                server_id: existing_server_id,
                uri: existing_uri,
                diagnostics: existing_diagnostics,
            } => {
                if server_id != *existing_server_id || &uri != existing_uri {
                    debug_panic!(
                        "Unexpected state: file {uri} has two different sets of diagnostics reported"
                    );
                }
                match existing_diagnostics {
                    PulledDiagnostics::Unchanged { .. } => {
                        *existing_diagnostics = PulledDiagnostics::Unchanged { result_id };
                    }
                    PulledDiagnostics::Changed { .. } => {}
                }
            }
        },
        hash_map::Entry::Vacant(v) => {
            v.insert(LspPullDiagnostics::Response {
                server_id,
                uri,
                diagnostics: PulledDiagnostics::Unchanged { result_id },
            });
        }
    }
}

fn process_full_diagnostics_report(
    diagnostics: &mut HashMap<lsp::Uri, LspPullDiagnostics>,
    server_id: LanguageServerId,
    uri: lsp::Uri,
    report: lsp::FullDocumentDiagnosticReport,
) {
    let result_id = report.result_id;
    match diagnostics.entry(uri.clone()) {
        hash_map::Entry::Occupied(mut o) => match o.get_mut() {
            LspPullDiagnostics::Default => {
                o.insert(LspPullDiagnostics::Response {
                    server_id,
                    uri,
                    diagnostics: PulledDiagnostics::Changed {
                        result_id,
                        diagnostics: report.items,
                    },
                });
            }
            LspPullDiagnostics::Response {
                server_id: existing_server_id,
                uri: existing_uri,
                diagnostics: existing_diagnostics,
            } => {
                if server_id != *existing_server_id || &uri != existing_uri {
                    debug_panic!(
                        "Unexpected state: file {uri} has two different sets of diagnostics reported"
                    );
                }
                match existing_diagnostics {
                    PulledDiagnostics::Unchanged { .. } => {
                        *existing_diagnostics = PulledDiagnostics::Changed {
                            result_id,
                            diagnostics: report.items,
                        };
                    }
                    PulledDiagnostics::Changed {
                        result_id: existing_result_id,
                        diagnostics: existing_diagnostics,
                    } => {
                        if result_id.is_some() {
                            *existing_result_id = result_id;
                        }
                        existing_diagnostics.extend(report.items);
                    }
                }
            }
        },
        hash_map::Entry::Vacant(v) => {
            v.insert(LspPullDiagnostics::Response {
                server_id,
                uri,
                diagnostics: PulledDiagnostics::Changed {
                    result_id,
                    diagnostics: report.items,
                },
            });
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use lsp::{DiagnosticSeverity, DiagnosticTag};
    use serde_json::json;

    #[test]
    fn test_serialize_lsp_diagnostic() {
        let lsp_diagnostic = lsp::Diagnostic {
            range: lsp::Range {
                start: lsp::Position::new(0, 1),
                end: lsp::Position::new(2, 3),
            },
            severity: Some(DiagnosticSeverity::ERROR),
            code: Some(lsp::NumberOrString::String("E001".to_string())),
            source: Some("test-source".to_string()),
            message: "Test error message".to_string(),
            related_information: None,
            tags: Some(vec![DiagnosticTag::DEPRECATED]),
            code_description: None,
            data: Some(json!({"detail": "test detail"})),
        };

        let proto_diagnostic = GetDocumentDiagnostics::serialize_lsp_diagnostic(lsp_diagnostic)
            .expect("Failed to serialize diagnostic");

        let start = proto_diagnostic.start.unwrap();
        let end = proto_diagnostic.end.unwrap();
        assert_eq!(start.row, 0);
        assert_eq!(start.column, 1);
        assert_eq!(end.row, 2);
        assert_eq!(end.column, 3);
        assert_eq!(
            proto_diagnostic.severity,
            proto::lsp_diagnostic::Severity::Error as i32
        );
        assert_eq!(proto_diagnostic.code, Some("E001".to_string()));
        assert_eq!(proto_diagnostic.source, Some("test-source".to_string()));
        assert_eq!(proto_diagnostic.message, "Test error message");
    }

    #[test]
    fn test_deserialize_lsp_diagnostic() {
        let proto_diagnostic = proto::LspDiagnostic {
            start: Some(proto::PointUtf16 { row: 0, column: 1 }),
            end: Some(proto::PointUtf16 { row: 2, column: 3 }),
            severity: proto::lsp_diagnostic::Severity::Warning as i32,
            code: Some("ERR".to_string()),
            source: Some("Prism".to_string()),
            message: "assigned but unused variable - a".to_string(),
            related_information: vec![],
            tags: vec![],
            code_description: None,
            data: None,
        };

        let lsp_diagnostic = GetDocumentDiagnostics::deserialize_lsp_diagnostic(proto_diagnostic)
            .expect("Failed to deserialize diagnostic");

        assert_eq!(lsp_diagnostic.range.start.line, 0);
        assert_eq!(lsp_diagnostic.range.start.character, 1);
        assert_eq!(lsp_diagnostic.range.end.line, 2);
        assert_eq!(lsp_diagnostic.range.end.character, 3);
        assert_eq!(lsp_diagnostic.severity, Some(DiagnosticSeverity::WARNING));
        assert_eq!(
            lsp_diagnostic.code,
            Some(lsp::NumberOrString::String("ERR".to_string()))
        );
        assert_eq!(lsp_diagnostic.source, Some("Prism".to_string()));
        assert_eq!(lsp_diagnostic.message, "assigned but unused variable - a");
    }

    #[test]
    fn test_related_information() {
        let related_info = lsp::DiagnosticRelatedInformation {
            location: lsp::Location {
                uri: lsp::Uri::from_str("file:///test.rs").unwrap(),
                range: lsp::Range {
                    start: lsp::Position::new(1, 1),
                    end: lsp::Position::new(1, 5),
                },
            },
            message: "Related info message".to_string(),
        };

        let lsp_diagnostic = lsp::Diagnostic {
            range: lsp::Range {
                start: lsp::Position::new(0, 0),
                end: lsp::Position::new(0, 1),
            },
            severity: Some(DiagnosticSeverity::INFORMATION),
            code: None,
            source: Some("Prism".to_string()),
            message: "assigned but unused variable - a".to_string(),
            related_information: Some(vec![related_info]),
            tags: None,
            code_description: None,
            data: None,
        };

        let proto_diagnostic = GetDocumentDiagnostics::serialize_lsp_diagnostic(lsp_diagnostic)
            .expect("Failed to serialize diagnostic");

        assert_eq!(proto_diagnostic.related_information.len(), 1);
        let related = &proto_diagnostic.related_information[0];
        assert_eq!(related.location_url, Some("file:///test.rs".to_string()));
        assert_eq!(related.message, "Related info message");
    }

    #[test]
    fn test_invalid_ranges() {
        let proto_diagnostic = proto::LspDiagnostic {
            start: None,
            end: Some(proto::PointUtf16 { row: 2, column: 3 }),
            severity: proto::lsp_diagnostic::Severity::Error as i32,
            code: None,
            source: None,
            message: "Test message".to_string(),
            related_information: vec![],
            tags: vec![],
            code_description: None,
            data: None,
        };

        let result = GetDocumentDiagnostics::deserialize_lsp_diagnostic(proto_diagnostic);
        assert!(result.is_err());
    }
}
